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::wrapper_traits::ThreadSafeLayoutNode;
7use malloc_size_of_derive::MallocSizeOf;
8use script::layout_dom::{ServoLayoutElement, ServoThreadSafeLayoutNode};
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    CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase,
23};
24use crate::positioned::PositioningContext;
25use crate::replaced::ReplacedContents;
26use crate::sizing::{
27    self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize,
28};
29use crate::style_ext::{AspectRatio, Display, DisplayInside, LayoutStyle};
30use crate::table::Table;
31use crate::taffy::TaffyContainer;
32use crate::{
33    ArcRefCell, ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, LogicalVec2,
34    PropagatedBoxTreeData,
35};
36
37/// <https://drafts.csswg.org/css-display/#independent-formatting-context>
38#[derive(Debug, MallocSizeOf)]
39pub(crate) struct IndependentFormattingContext {
40    pub base: LayoutBoxBase,
41    // Private so that code outside of this module cannot match variants.
42    // It should go through methods instead.
43    contents: IndependentFormattingContextContents,
44    /// Data that was originally propagated down to this [`IndependentFormattingContext`]
45    /// during creation. This is used during incremental layout.
46    pub propagated_data: PropagatedBoxTreeData,
47}
48
49#[derive(Debug, MallocSizeOf)]
50pub(crate) enum IndependentFormattingContextContents {
51    // Additionally to the replaced contents, replaced boxes may have an inner widget.
52    Replaced(
53        ReplacedContents,
54        Option<ArcRefCell<IndependentFormattingContext>>,
55    ),
56    Flow(BlockFormattingContext),
57    Flex(FlexContainer),
58    Grid(TaffyContainer),
59    Table(Table),
60    // Other layout modes go here
61}
62
63/// The baselines of a layout or a [`crate::fragment_tree::BoxFragment`]. Some layout
64/// uses the first and some layout uses the last.
65#[derive(Clone, Copy, Debug, Default, MallocSizeOf)]
66pub(crate) struct Baselines {
67    pub first: Option<Au>,
68    pub last: Option<Au>,
69}
70
71impl Baselines {
72    pub(crate) fn offset(&self, block_offset: Au) -> Baselines {
73        Self {
74            first: self.first.map(|first| first + block_offset),
75            last: self.last.map(|last| last + block_offset),
76        }
77    }
78}
79
80impl IndependentFormattingContext {
81    pub(crate) fn new(
82        base: LayoutBoxBase,
83        contents: IndependentFormattingContextContents,
84        propagated_data: PropagatedBoxTreeData,
85    ) -> Self {
86        Self {
87            base,
88            contents,
89            propagated_data,
90        }
91    }
92
93    pub(crate) fn rebuild(
94        &mut self,
95        layout_context: &LayoutContext,
96        node_and_style_info: &NodeAndStyleInfo,
97    ) {
98        let contents = Contents::for_element(node_and_style_info.node, layout_context);
99        let display = match Display::from(node_and_style_info.style.get_box().display) {
100            Display::None | Display::Contents => {
101                unreachable!("Should never try to rebuild IndependentFormattingContext with no box")
102            },
103            Display::GeneratingBox(display) => display.used_value_for_contents(&contents),
104        };
105        self.contents = Self::construct_contents(
106            layout_context,
107            node_and_style_info,
108            &mut self.base.base_fragment_info,
109            display.display_inside(),
110            contents,
111            self.propagated_data,
112        );
113
114        self.base.clear_fragments_and_fragment_cache();
115        *self.base.cached_inline_content_size.borrow_mut() = None;
116        self.base.repair_style(&node_and_style_info.style);
117    }
118
119    pub(crate) fn construct(
120        context: &LayoutContext,
121        node_and_style_info: &NodeAndStyleInfo,
122        display_inside: DisplayInside,
123        contents: Contents,
124        propagated_data: PropagatedBoxTreeData,
125    ) -> Self {
126        let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into();
127        let contents = Self::construct_contents(
128            context,
129            node_and_style_info,
130            &mut base_fragment_info,
131            display_inside,
132            contents,
133            propagated_data,
134        );
135        Self {
136            base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()),
137            contents,
138            propagated_data,
139        }
140    }
141
142    fn construct_contents(
143        context: &LayoutContext,
144        node_and_style_info: &NodeAndStyleInfo,
145        base_fragment_info: &mut BaseFragmentInfo,
146        display_inside: DisplayInside,
147        contents: Contents,
148        propagated_data: PropagatedBoxTreeData,
149    ) -> IndependentFormattingContextContents {
150        let non_replaced_contents = match contents {
151            Contents::Replaced(contents) => {
152                base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
153                // Some replaced elements can have inner widgets, e.g. `<video controls>`.
154                let widget = Some(node_and_style_info.node)
155                    .filter(|node| node.pseudo_element_chain().is_empty())
156                    .and_then(|node| node.as_element())
157                    .and_then(|element| element.shadow_root())
158                    .is_some_and(|shadow_root| shadow_root.is_ua_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 =
174                            LayoutBoxBase::new((&widget_info).into(), widget_info.style);
175                        ArcRefCell::new(IndependentFormattingContext::new(
176                            widget_base,
177                            widget_contents,
178                            propagated_data,
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::<ServoLayoutElement>(
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    ) -> Option<ContentSizes> {
270        // See <https://github.com/w3c/csswg-drafts/issues/12333> regarding the difference
271        // in behavior for the replaced and non-replaced cases.
272        match &self.contents {
273            IndependentFormattingContextContents::Replaced(contents, _) => {
274                // For replaced elements with no ratio, the returned value doesn't matter.
275                let ratio = preferred_aspect_ratio?;
276                let writing_mode = self.style().writing_mode;
277                let natural_sizes = contents.logical_natural_sizes(writing_mode);
278                let block_size = match (natural_sizes.block, natural_sizes.inline) {
279                    (Some(block_size), None) => block_size,
280                    _ => {
281                        let inline_size = contents.fallback_inline_size(writing_mode);
282                        ratio.compute_dependent_size(Direction::Block, inline_size)
283                    },
284                };
285                Some(block_size.into())
286            },
287            _ => None,
288        }
289    }
290
291    pub(crate) fn outer_inline_content_sizes(
292        &self,
293        layout_context: &LayoutContext,
294        containing_block: &IndefiniteContainingBlock,
295        auto_minimum: &LogicalVec2<Au>,
296        auto_block_size_stretches_to_containing_block: bool,
297    ) -> InlineContentSizesResult {
298        sizing::outer_inline(
299            &self.base,
300            &self.layout_style(),
301            containing_block,
302            auto_minimum,
303            auto_block_size_stretches_to_containing_block,
304            self.is_replaced(),
305            true, /* establishes_containing_block */
306            |padding_border_sums| self.preferred_aspect_ratio(padding_border_sums),
307            |constraint_space| self.inline_content_sizes(layout_context, constraint_space),
308            |preferred_aspect_ratio| self.tentative_block_content_size(preferred_aspect_ratio),
309        )
310    }
311
312    pub(crate) fn repair_style(
313        &mut self,
314        context: &SharedStyleContext,
315        node: &ServoThreadSafeLayoutNode,
316        new_style: &Arc<ComputedValues>,
317    ) {
318        self.base.repair_style(new_style);
319        match &mut self.contents {
320            IndependentFormattingContextContents::Replaced(_, widget) => {
321                if let Some(widget) = widget {
322                    let node = node
323                        .with_pseudo(PseudoElement::ServoAnonymousBox)
324                        .expect("Should always be able to construct info for anonymous boxes.");
325                    widget.borrow_mut().repair_style(context, &node, new_style);
326                }
327            },
328            IndependentFormattingContextContents::Flow(block_formatting_context) => {
329                block_formatting_context.repair_style(context, node, new_style);
330            },
331            IndependentFormattingContextContents::Flex(flex_container) => {
332                flex_container.repair_style(new_style)
333            },
334            IndependentFormattingContextContents::Grid(taffy_container) => {
335                taffy_container.repair_style(new_style)
336            },
337            IndependentFormattingContextContents::Table(table) => {
338                table.repair_style(context, new_style)
339            },
340        }
341    }
342
343    #[inline]
344    pub(crate) fn is_block_container(&self) -> bool {
345        matches!(self.contents, IndependentFormattingContextContents::Flow(_))
346    }
347
348    #[inline]
349    pub(crate) fn is_replaced(&self) -> bool {
350        matches!(
351            self.contents,
352            IndependentFormattingContextContents::Replaced(_, _)
353        )
354    }
355
356    #[inline]
357    pub(crate) fn is_table(&self) -> bool {
358        matches!(
359            &self.contents,
360            IndependentFormattingContextContents::Table(_)
361        )
362    }
363
364    fn layout_without_caching(
365        &self,
366        layout_context: &LayoutContext,
367        positioning_context: &mut PositioningContext,
368        containing_block_for_children: &ContainingBlock,
369        containing_block: &ContainingBlock,
370        preferred_aspect_ratio: Option<AspectRatio>,
371        lazy_block_size: &LazySize,
372    ) -> CacheableLayoutResult {
373        match &self.contents {
374            IndependentFormattingContextContents::Replaced(replaced, widget) => {
375                let mut replaced_layout = replaced.layout(
376                    layout_context,
377                    containing_block_for_children,
378                    preferred_aspect_ratio,
379                    &self.base,
380                    lazy_block_size,
381                );
382                if let Some(widget) = widget {
383                    let mut widget_layout = widget.borrow().layout(
384                        layout_context,
385                        positioning_context,
386                        containing_block_for_children,
387                        containing_block_for_children,
388                        None,
389                        &LazySize::intrinsic(),
390                    );
391                    replaced_layout
392                        .fragments
393                        .append(&mut widget_layout.fragments);
394                }
395                replaced_layout
396            },
397            IndependentFormattingContextContents::Flow(bfc) => bfc.layout(
398                layout_context,
399                positioning_context,
400                containing_block_for_children,
401            ),
402            IndependentFormattingContextContents::Flex(fc) => fc.layout(
403                layout_context,
404                positioning_context,
405                containing_block_for_children,
406                lazy_block_size,
407            ),
408            IndependentFormattingContextContents::Grid(fc) => fc.layout(
409                layout_context,
410                positioning_context,
411                containing_block_for_children,
412                containing_block,
413            ),
414            IndependentFormattingContextContents::Table(table) => table.layout(
415                layout_context,
416                positioning_context,
417                containing_block_for_children,
418                containing_block,
419            ),
420        }
421    }
422
423    #[servo_tracing::instrument(name = "IndependentFormattingContext::layout", skip_all)]
424    pub(crate) fn layout(
425        &self,
426        layout_context: &LayoutContext,
427        positioning_context: &mut PositioningContext,
428        containing_block_for_children: &ContainingBlock,
429        containing_block: &ContainingBlock,
430        preferred_aspect_ratio: Option<AspectRatio>,
431        lazy_block_size: &LazySize,
432    ) -> CacheableLayoutResult {
433        if let Some(cache) = self.base.cached_layout_result.borrow().as_ref() {
434            let cache = &**cache;
435            if cache.containing_block_for_children_size.inline ==
436                containing_block_for_children.size.inline &&
437                (cache.containing_block_for_children_size.block ==
438                    containing_block_for_children.size.block ||
439                    !cache.result.depends_on_block_constraints)
440            {
441                positioning_context.append(cache.positioning_context.clone());
442                return cache.result.clone();
443            }
444            #[cfg(feature = "tracing")]
445            tracing::debug!(
446                name: "IndependentFormattingContext::layout cache miss",
447                cached = ?cache.containing_block_for_children_size,
448                required = ?containing_block_for_children.size,
449            );
450        }
451
452        let mut child_positioning_context = PositioningContext::default();
453        let result = self.layout_without_caching(
454            layout_context,
455            &mut child_positioning_context,
456            containing_block_for_children,
457            containing_block,
458            preferred_aspect_ratio,
459            lazy_block_size,
460        );
461
462        *self.base.cached_layout_result.borrow_mut() =
463            Some(Box::new(CacheableLayoutResultAndInputs {
464                result: result.clone(),
465                positioning_context: child_positioning_context.clone(),
466                containing_block_for_children_size: containing_block_for_children.size.clone(),
467            }));
468        positioning_context.append(child_positioning_context);
469
470        result
471    }
472
473    #[inline]
474    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
475        match &self.contents {
476            IndependentFormattingContextContents::Replaced(replaced, _) => {
477                replaced.layout_style(&self.base)
478            },
479            IndependentFormattingContextContents::Flow(fc) => fc.layout_style(&self.base),
480            IndependentFormattingContextContents::Flex(fc) => fc.layout_style(),
481            IndependentFormattingContextContents::Grid(fc) => fc.layout_style(),
482            IndependentFormattingContextContents::Table(fc) => fc.layout_style(None),
483        }
484    }
485
486    #[inline]
487    pub(crate) fn preferred_aspect_ratio(
488        &self,
489        padding_border_sums: &LogicalVec2<Au>,
490    ) -> Option<AspectRatio> {
491        match &self.contents {
492            IndependentFormattingContextContents::Replaced(replaced, _) => {
493                replaced.preferred_aspect_ratio(self.style(), padding_border_sums)
494            },
495            // TODO: support preferred aspect ratios on non-replaced boxes.
496            _ => None,
497        }
498    }
499
500    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
501        match &self.contents {
502            IndependentFormattingContextContents::Replaced(_, widget) => {
503                if let Some(widget) = widget {
504                    widget.borrow_mut().base.parent_box.replace(layout_box);
505                }
506            },
507            IndependentFormattingContextContents::Flow(contents) => {
508                contents.attached_to_tree(layout_box)
509            },
510            IndependentFormattingContextContents::Flex(contents) => {
511                contents.attached_to_tree(layout_box)
512            },
513            IndependentFormattingContextContents::Grid(contents) => {
514                contents.attached_to_tree(layout_box)
515            },
516            IndependentFormattingContextContents::Table(contents) => {
517                contents.attached_to_tree(layout_box)
518            },
519        }
520    }
521}
522
523impl ComputeInlineContentSizes for IndependentFormattingContextContents {
524    fn compute_inline_content_sizes(
525        &self,
526        layout_context: &LayoutContext,
527        constraint_space: &ConstraintSpace,
528    ) -> InlineContentSizesResult {
529        match self {
530            Self::Replaced(inner, _) => {
531                inner.compute_inline_content_sizes(layout_context, constraint_space)
532            },
533            Self::Flow(inner) => inner
534                .contents
535                .compute_inline_content_sizes(layout_context, constraint_space),
536            Self::Flex(inner) => {
537                inner.compute_inline_content_sizes(layout_context, constraint_space)
538            },
539            Self::Grid(inner) => {
540                inner.compute_inline_content_sizes(layout_context, constraint_space)
541            },
542            Self::Table(inner) => {
543                inner.compute_inline_content_sizes(layout_context, constraint_space)
544            },
545        }
546    }
547}