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