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
130        // This ensures that the `FragmentFlags` of this `BaseFragmentInfo` reflect the
131        // current layout and not the set that was calculated during previous layouts.
132        self.base.base_fragment_info = node_and_style_info.into();
133
134        self.contents = Self::construct_contents(
135            layout_context,
136            node_and_style_info,
137            &mut self.base.base_fragment_info,
138            display.display_inside(),
139            contents,
140            self.propagated_data,
141        );
142
143        self.base.clear_fragments_and_dirty_fragment_cache();
144        *self.base.cached_inline_content_size.borrow_mut() = None;
145        self.base.repair_style(&node_and_style_info.style);
146    }
147
148    pub(crate) fn construct(
149        context: &LayoutContext,
150        node_and_style_info: &NodeAndStyleInfo,
151        display_inside: DisplayInside,
152        contents: Contents,
153        propagated_data: PropagatedBoxTreeData,
154    ) -> Self {
155        let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into();
156        let contents = Self::construct_contents(
157            context,
158            node_and_style_info,
159            &mut base_fragment_info,
160            display_inside,
161            contents,
162            propagated_data,
163        );
164
165        let base = LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone());
166        base.set_subtree_size(contents.subtree_size() + 1);
167
168        Self {
169            base,
170            contents,
171            propagated_data,
172            layout_root_layout_inputs: None.into(),
173        }
174    }
175
176    fn construct_contents(
177        context: &LayoutContext,
178        node_and_style_info: &NodeAndStyleInfo,
179        base_fragment_info: &mut BaseFragmentInfo,
180        display_inside: DisplayInside,
181        contents: Contents,
182        propagated_data: PropagatedBoxTreeData,
183    ) -> IndependentFormattingContextContents {
184        let non_replaced_contents = match contents {
185            Contents::Replaced(contents) => {
186                base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
187
188                // Some replaced elements can have inner widgets, e.g. `<video controls>`.
189                let node = node_and_style_info.node;
190                let should_make_widget = node.pseudo_element_chain().is_empty() &&
191                    node.is_root_of_user_agent_widget() &&
192                    !contents.is_content_replacement;
193                let widget = should_make_widget.then(|| {
194                    let widget_info = node_and_style_info
195                        .with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
196                        .expect("Should always be able to construct info for anonymous boxes.");
197                    // Use a block formatting context for the widget, since the display inside is always flow.
198                    let widget_contents = IndependentFormattingContextContents::Flow(
199                        BlockFormattingContext::construct(
200                            context,
201                            &widget_info,
202                            NonReplacedContents::OfElement,
203                            propagated_data,
204                            false, /* is_list_item */
205                        ),
206                    );
207                    let widget_base = LayoutBoxBase::new((&widget_info).into(), widget_info.style);
208                    ArcRefCell::new(IndependentFormattingContext::new(
209                        widget_base,
210                        widget_contents,
211                        propagated_data,
212                    ))
213                });
214
215                return IndependentFormattingContextContents::Replaced(contents, widget);
216            },
217            Contents::Widget(non_replaced_contents) => {
218                base_fragment_info.flags.insert(FragmentFlags::IS_WIDGET);
219                non_replaced_contents
220            },
221            Contents::NonReplaced(non_replaced_contents) => non_replaced_contents,
222        };
223
224        match display_inside {
225            DisplayInside::Flow { is_list_item } | DisplayInside::FlowRoot { is_list_item } => {
226                IndependentFormattingContextContents::Flow(BlockFormattingContext::construct(
227                    context,
228                    node_and_style_info,
229                    non_replaced_contents,
230                    propagated_data,
231                    is_list_item,
232                ))
233            },
234            DisplayInside::Grid => {
235                IndependentFormattingContextContents::Grid(TaffyContainer::construct(
236                    context,
237                    node_and_style_info,
238                    non_replaced_contents,
239                    propagated_data,
240                ))
241            },
242            DisplayInside::Flex => {
243                IndependentFormattingContextContents::Flex(FlexContainer::construct(
244                    context,
245                    node_and_style_info,
246                    non_replaced_contents,
247                    propagated_data,
248                ))
249            },
250            DisplayInside::Table => {
251                let table_grid_style = context
252                    .style_context
253                    .stylist
254                    .style_for_anonymous::<ServoDangerousStyleElement>(
255                        &context.style_context.guards,
256                        &PseudoElement::ServoTableGrid,
257                        &node_and_style_info.style,
258                    );
259                base_fragment_info.flags.insert(FragmentFlags::DO_NOT_PAINT);
260                IndependentFormattingContextContents::Table(Table::construct(
261                    context,
262                    node_and_style_info,
263                    table_grid_style,
264                    non_replaced_contents,
265                    propagated_data,
266                ))
267            },
268        }
269    }
270
271    #[inline]
272    pub fn style(&self) -> &Arc<ComputedValues> {
273        &self.base.style
274    }
275
276    #[inline]
277    pub fn base_fragment_info(&self) -> BaseFragmentInfo {
278        self.base.base_fragment_info
279    }
280
281    pub(crate) fn inline_content_sizes(
282        &self,
283        layout_context: &LayoutContext,
284        constraint_space: &ConstraintSpace,
285    ) -> InlineContentSizesResult {
286        self.base
287            .inline_content_sizes(layout_context, constraint_space, &self.contents)
288    }
289
290    /// Computes the tentative intrinsic block sizes that may be needed while computing
291    /// the intrinsic inline sizes. Therefore, this ignores the values of the sizing
292    /// properties in both axes.
293    /// A return value of `None` indicates that there is no suitable tentative intrinsic
294    /// block size, so intrinsic keywords in the block sizing properties will be ignored,
295    /// possibly resulting in an indefinite [`SizeConstraint`] for computing the intrinsic
296    /// inline sizes and laying out the contents.
297    /// A return value of `Some` indicates that intrinsic keywords in the block sizing
298    /// properties will be resolved as the contained value, guaranteeing a definite amount
299    /// for computing the intrinsic inline sizes and laying out the contents.
300    pub(crate) fn tentative_block_content_size(
301        &self,
302        preferred_aspect_ratio: Option<AspectRatio>,
303        inline_stretch_size: Au,
304    ) -> Option<ContentSizes> {
305        let result = self.tentative_block_content_size_with_dependency(
306            preferred_aspect_ratio,
307            inline_stretch_size,
308        );
309        Some(result?.0)
310    }
311
312    /// Same as [`Self::tentative_block_content_size()`], but if there is a tentative intrinsic
313    /// block size, it also includes a bool which will be true if the former depends on
314    /// the provided `inline_stretch_size`.
315    pub(crate) fn tentative_block_content_size_with_dependency(
316        &self,
317        preferred_aspect_ratio: Option<AspectRatio>,
318        inline_stretch_size: Au,
319    ) -> Option<(ContentSizes, bool)> {
320        // See <https://github.com/w3c/csswg-drafts/issues/12333> regarding the difference
321        // in behavior for the replaced and non-replaced cases.
322        match &self.contents {
323            IndependentFormattingContextContents::Replaced(contents, _) => {
324                // For replaced elements with no ratio, the returned value doesn't matter.
325                let ratio = preferred_aspect_ratio?;
326                let writing_mode = self.style().writing_mode;
327                let natural_sizes = contents.logical_natural_sizes(writing_mode);
328                let (block_size, depends_on_inline_stretch_size) =
329                    match (natural_sizes.block, natural_sizes.inline) {
330                        (Some(block_size), None) => (block_size, false),
331                        (_, Some(inline_size)) => (
332                            ratio.compute_dependent_size(Direction::Block, inline_size),
333                            false,
334                        ),
335                        (None, None) => (
336                            ratio.compute_dependent_size(Direction::Block, inline_stretch_size),
337                            true,
338                        ),
339                    };
340                Some((block_size.into(), depends_on_inline_stretch_size))
341            },
342            _ => None,
343        }
344    }
345
346    pub(crate) fn outer_inline_content_sizes(
347        &self,
348        layout_context: &LayoutContext,
349        containing_block: &IndefiniteContainingBlock,
350        auto_minimum: &LogicalVec2<Au>,
351        auto_block_size_stretches_to_containing_block: bool,
352    ) -> InlineContentSizesResult {
353        sizing::outer_inline(
354            &self.base,
355            &self.layout_style(),
356            containing_block,
357            auto_minimum,
358            auto_block_size_stretches_to_containing_block,
359            self.is_replaced(),
360            true, /* establishes_containing_block */
361            |padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),
362            |constraint_space| self.inline_content_sizes(layout_context, constraint_space),
363            |preferred_aspect_ratio| {
364                self.tentative_block_content_size(preferred_aspect_ratio, Au(0))
365            },
366        )
367    }
368
369    pub(crate) fn repair_style(
370        &mut self,
371        context: &SharedStyleContext,
372        node: &ServoLayoutNode,
373        new_style: &Arc<ComputedValues>,
374    ) {
375        self.base.repair_style(new_style);
376        match &mut self.contents {
377            IndependentFormattingContextContents::Replaced(_, widget) => {
378                if let Some(widget) = widget {
379                    let node = node
380                        .with_pseudo(PseudoElement::ServoAnonymousBox)
381                        .expect("Should always be able to construct info for anonymous boxes.");
382                    widget.borrow_mut().repair_style(context, &node, new_style);
383                }
384            },
385            IndependentFormattingContextContents::Flow(block_formatting_context) => {
386                block_formatting_context.repair_style(context, node, new_style);
387            },
388            IndependentFormattingContextContents::Flex(flex_container) => {
389                flex_container.repair_style(new_style)
390            },
391            IndependentFormattingContextContents::Grid(taffy_container) => {
392                taffy_container.repair_style(new_style)
393            },
394            IndependentFormattingContextContents::Table(table) => {
395                table.repair_style(context, new_style)
396            },
397        }
398    }
399
400    #[inline]
401    pub(crate) fn is_block_container(&self) -> bool {
402        matches!(self.contents, IndependentFormattingContextContents::Flow(_))
403    }
404
405    #[inline]
406    pub(crate) fn is_replaced(&self) -> bool {
407        matches!(
408            self.contents,
409            IndependentFormattingContextContents::Replaced(_, _)
410        )
411    }
412
413    #[inline]
414    pub(crate) fn is_table(&self) -> bool {
415        matches!(
416            &self.contents,
417            IndependentFormattingContextContents::Table(_)
418        )
419    }
420
421    #[inline]
422    pub(crate) fn is_grid(&self) -> bool {
423        matches!(
424            &self.contents,
425            IndependentFormattingContextContents::Grid(_)
426        )
427    }
428
429    #[servo_tracing::instrument(
430        name = "IndependentFormattingContext::layout_without_caching",
431        skip_all
432    )]
433    fn layout_without_caching(
434        &self,
435        layout_context: &LayoutContext,
436        positioning_context: &mut PositioningContext,
437        containing_block_for_children: &ContainingBlock,
438        containing_block: &ContainingBlock,
439        preferred_aspect_ratio: Option<AspectRatio>,
440        lazy_block_size: &LazySize,
441    ) -> IndependentFormattingContextLayoutResult {
442        match &self.contents {
443            IndependentFormattingContextContents::Replaced(replaced, widget) => {
444                let mut replaced_layout = replaced.layout(
445                    layout_context,
446                    containing_block_for_children,
447                    preferred_aspect_ratio,
448                    &self.base,
449                    lazy_block_size,
450                );
451                if let Some(widget) = widget {
452                    let mut widget_layout = widget.borrow().layout(
453                        layout_context,
454                        positioning_context,
455                        containing_block_for_children,
456                        containing_block_for_children,
457                        None,
458                        &LazySize::intrinsic(),
459                    );
460                    replaced_layout
461                        .fragments
462                        .append(&mut widget_layout.fragments);
463                }
464                replaced_layout
465            },
466            IndependentFormattingContextContents::Flow(bfc) => bfc.layout(
467                layout_context,
468                positioning_context,
469                containing_block_for_children,
470            ),
471            IndependentFormattingContextContents::Flex(fc) => fc.layout(
472                layout_context,
473                positioning_context,
474                containing_block_for_children,
475                lazy_block_size,
476            ),
477            IndependentFormattingContextContents::Grid(fc) => fc.layout(
478                layout_context,
479                positioning_context,
480                containing_block_for_children,
481                containing_block,
482            ),
483            IndependentFormattingContextContents::Table(table) => table.layout(
484                layout_context,
485                positioning_context,
486                containing_block_for_children,
487                containing_block,
488            ),
489        }
490    }
491
492    pub(crate) fn layout_and_is_cached(
493        &self,
494        layout_context: &LayoutContext,
495        positioning_context: &mut PositioningContext,
496        containing_block_for_children: &ContainingBlock,
497        containing_block: &ContainingBlock,
498        preferred_aspect_ratio: Option<AspectRatio>,
499        lazy_block_size: &LazySize,
500    ) -> (IndependentFormattingContextLayoutResult, bool) {
501        if let Some(cached_layout_result) = self
502            .base
503            .cached_independent_formatting_context_layout_if_applicable(
504                positioning_context,
505                containing_block_for_children,
506            )
507        {
508            return (cached_layout_result, true);
509        }
510
511        #[cfg(feature = "tracing")]
512        tracing::debug!(
513            name: "IndependentFormattingContext::layout cache miss",
514            required = ?containing_block_for_children.size,
515        );
516        let mut child_positioning_context = PositioningContext::default();
517        let result = self.layout_without_caching(
518            layout_context,
519            &mut child_positioning_context,
520            containing_block_for_children,
521            containing_block,
522            preferred_aspect_ratio,
523            lazy_block_size,
524        );
525        self.base.cache_independent_formatting_context_layout(
526            containing_block_for_children,
527            &child_positioning_context,
528            &result,
529        );
530        positioning_context.append(child_positioning_context);
531        (result, false)
532    }
533
534    pub(crate) fn layout(
535        &self,
536        layout_context: &LayoutContext,
537        positioning_context: &mut PositioningContext,
538        containing_block_for_children: &ContainingBlock,
539        containing_block: &ContainingBlock,
540        preferred_aspect_ratio: Option<AspectRatio>,
541        lazy_block_size: &LazySize,
542    ) -> IndependentFormattingContextLayoutResult {
543        self.layout_and_is_cached(
544            layout_context,
545            positioning_context,
546            containing_block_for_children,
547            containing_block,
548            preferred_aspect_ratio,
549            lazy_block_size,
550        )
551        .0
552    }
553
554    #[inline]
555    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
556        match &self.contents {
557            IndependentFormattingContextContents::Replaced(replaced, _) => {
558                replaced.layout_style(&self.base)
559            },
560            IndependentFormattingContextContents::Flow(fc) => fc.layout_style(&self.base),
561            IndependentFormattingContextContents::Flex(fc) => fc.layout_style(),
562            IndependentFormattingContextContents::Grid(fc) => fc.layout_style(),
563            IndependentFormattingContextContents::Table(fc) => fc.layout_style(None),
564        }
565    }
566
567    #[inline]
568    pub(crate) fn preferred_aspect_ratio(
569        &self,
570        padding_border_sums: &LogicalVec2<Au>,
571    ) -> Option<AspectRatio> {
572        match &self.contents {
573            IndependentFormattingContextContents::Replaced(replaced, _) => {
574                replaced.preferred_aspect_ratio(self.style(), padding_border_sums)
575            },
576            // TODO: support preferred aspect ratios on non-replaced boxes.
577            _ => None,
578        }
579    }
580
581    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
582        match &self.contents {
583            IndependentFormattingContextContents::Replaced(_, widget) => {
584                if let Some(widget) = widget {
585                    widget.borrow_mut().base.parent_box.replace(layout_box);
586                }
587            },
588            IndependentFormattingContextContents::Flow(contents) => {
589                contents.attached_to_tree(layout_box)
590            },
591            IndependentFormattingContextContents::Flex(contents) => {
592                contents.attached_to_tree(layout_box)
593            },
594            IndependentFormattingContextContents::Grid(contents) => {
595                contents.attached_to_tree(layout_box)
596            },
597            IndependentFormattingContextContents::Table(contents) => {
598                contents.attached_to_tree(layout_box)
599            },
600        }
601    }
602
603    pub(crate) fn subtree_size(&self) -> usize {
604        self.base.subtree_size()
605    }
606}
607
608impl ComputeInlineContentSizes for IndependentFormattingContextContents {
609    fn compute_inline_content_sizes(
610        &self,
611        layout_context: &LayoutContext,
612        constraint_space: &ConstraintSpace,
613    ) -> InlineContentSizesResult {
614        match self {
615            Self::Replaced(inner, _) => {
616                inner.compute_inline_content_sizes(layout_context, constraint_space)
617            },
618            Self::Flow(inner) => inner
619                .contents
620                .compute_inline_content_sizes(layout_context, constraint_space),
621            Self::Flex(inner) => {
622                inner.compute_inline_content_sizes(layout_context, constraint_space)
623            },
624            Self::Grid(inner) => {
625                inner.compute_inline_content_sizes(layout_context, constraint_space)
626            },
627            Self::Table(inner) => {
628                inner.compute_inline_content_sizes(layout_context, constraint_space)
629            },
630        }
631    }
632}