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