layout/
construct_modern.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
5//! Layout construction code that is shared between modern layout modes (Flexbox and CSS Grid)
6
7use std::borrow::Cow;
8use std::sync::LazyLock;
9
10use rayon::iter::{IntoParallelIterator, ParallelIterator};
11use style::selector_parser::PseudoElement;
12
13use crate::PropagatedBoxTreeData;
14use crate::context::LayoutContext;
15use crate::dom::{BoxSlot, LayoutBox};
16use crate::dom_traversal::{Contents, NodeAndStyleInfo, TraversalHandler};
17use crate::flow::inline::construct::InlineFormattingContextBuilder;
18use crate::flow::{BlockContainer, BlockFormattingContext};
19use crate::formatting_contexts::{
20    IndependentFormattingContext, IndependentFormattingContextContents,
21};
22use crate::layout_box_base::LayoutBoxBase;
23use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox};
24
25/// A builder used for both flex and grid containers.
26pub(crate) struct ModernContainerBuilder<'a, 'dom> {
27    context: &'a LayoutContext<'a>,
28    info: &'a NodeAndStyleInfo<'dom>,
29    propagated_data: PropagatedBoxTreeData,
30    contiguous_text_runs: Vec<ModernContainerTextRun<'dom>>,
31    /// To be run in parallel with rayon in `finish`
32    jobs: Vec<ModernContainerJob<'dom>>,
33    has_text_runs: bool,
34}
35
36enum ModernContainerJob<'dom> {
37    ElementOrPseudoElement {
38        info: NodeAndStyleInfo<'dom>,
39        display: DisplayGeneratingBox,
40        contents: Contents,
41        box_slot: BoxSlot<'dom>,
42    },
43    TextRuns(Vec<ModernContainerTextRun<'dom>>),
44}
45
46impl<'dom> ModernContainerJob<'dom> {
47    fn finish(
48        self,
49        builder: &ModernContainerBuilder,
50        anonymous_info: &LazyLock<NodeAndStyleInfo<'dom>, impl FnOnce() -> NodeAndStyleInfo<'dom>>,
51    ) -> Option<ModernItem<'dom>> {
52        match self {
53            ModernContainerJob::TextRuns(runs) => {
54                let mut inline_formatting_context_builder =
55                    InlineFormattingContextBuilder::new(builder.info);
56                for flex_text_run in runs.into_iter() {
57                    inline_formatting_context_builder
58                        .push_text(flex_text_run.text, &flex_text_run.info);
59                }
60
61                let inline_formatting_context = inline_formatting_context_builder.finish(
62                    builder.context,
63                    true,  /* has_first_formatted_line */
64                    false, /* is_single_line_text_box */
65                    builder.info.style.to_bidi_level(),
66                    builder.context.rendering_group_id,
67                )?;
68
69                let block_formatting_context = BlockFormattingContext::from_block_container(
70                    BlockContainer::InlineFormattingContext(inline_formatting_context),
71                );
72                let info: &NodeAndStyleInfo = anonymous_info;
73                let formatting_context = IndependentFormattingContext::new(
74                    LayoutBoxBase::new(info.into(), info.style.clone()),
75                    IndependentFormattingContextContents::Flow(block_formatting_context),
76                );
77
78                Some(ModernItem {
79                    kind: ModernItemKind::InFlow(formatting_context),
80                    order: 0,
81                    box_slot: None,
82                })
83            },
84            ModernContainerJob::ElementOrPseudoElement {
85                info,
86                display,
87                contents,
88                box_slot,
89            } => {
90                let is_abspos = info.style.get_box().position.is_absolutely_positioned();
91                let order = if is_abspos {
92                    0
93                } else {
94                    info.style.clone_order()
95                };
96
97                if let Some(layout_box) = box_slot
98                    .take_layout_box_if_undamaged(info.damage)
99                    .and_then(|layout_box| match &layout_box {
100                        LayoutBox::FlexLevel(_) | LayoutBox::TaffyItemBox(_) => Some(layout_box),
101                        _ => None,
102                    })
103                {
104                    return Some(ModernItem {
105                        kind: ModernItemKind::ReusedBox(layout_box),
106                        order,
107                        box_slot: Some(box_slot),
108                    });
109                }
110
111                // Text decorations are not propagated to any out-of-flow descendants. In addition,
112                // absolutes don't affect the size of ancestors so it is fine to allow descendent
113                // tables to resolve percentage columns.
114                let propagated_data = match is_abspos {
115                    false => builder.propagated_data,
116                    true => PropagatedBoxTreeData::default(),
117                };
118
119                let formatting_context = IndependentFormattingContext::construct(
120                    builder.context,
121                    &info,
122                    display.display_inside(),
123                    contents,
124                    propagated_data,
125                );
126
127                let kind = if is_abspos {
128                    ModernItemKind::OutOfFlow(formatting_context)
129                } else {
130                    ModernItemKind::InFlow(formatting_context)
131                };
132                Some(ModernItem {
133                    kind,
134                    order,
135                    box_slot: Some(box_slot),
136                })
137            },
138        }
139    }
140}
141
142struct ModernContainerTextRun<'dom> {
143    info: NodeAndStyleInfo<'dom>,
144    text: Cow<'dom, str>,
145}
146
147impl ModernContainerTextRun<'_> {
148    /// <https://drafts.csswg.org/css-text/#white-space>
149    fn is_only_document_white_space(&self) -> bool {
150        // FIXME: is this the right definition? See
151        // https://github.com/w3c/csswg-drafts/issues/5146
152        // https://github.com/w3c/csswg-drafts/issues/5147
153        self.text
154            .bytes()
155            .all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
156    }
157}
158
159pub(crate) enum ModernItemKind {
160    InFlow(IndependentFormattingContext),
161    OutOfFlow(IndependentFormattingContext),
162    ReusedBox(LayoutBox),
163}
164
165pub(crate) struct ModernItem<'dom> {
166    pub kind: ModernItemKind,
167    pub order: i32,
168    pub box_slot: Option<BoxSlot<'dom>>,
169}
170
171impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> {
172    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
173        self.contiguous_text_runs.push(ModernContainerTextRun {
174            info: info.clone(),
175            text,
176        })
177    }
178
179    /// Or pseudo-element
180    fn handle_element(
181        &mut self,
182        info: &NodeAndStyleInfo<'dom>,
183        display: DisplayGeneratingBox,
184        contents: Contents,
185        box_slot: BoxSlot<'dom>,
186    ) {
187        self.wrap_any_text_in_anonymous_block_container();
188
189        self.jobs.push(ModernContainerJob::ElementOrPseudoElement {
190            info: info.clone(),
191            display,
192            contents,
193            box_slot,
194        })
195    }
196}
197
198impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
199    pub fn new(
200        context: &'a LayoutContext<'a>,
201        info: &'a NodeAndStyleInfo<'dom>,
202        propagated_data: PropagatedBoxTreeData,
203    ) -> Self {
204        ModernContainerBuilder {
205            context,
206            info,
207            propagated_data: propagated_data.disallowing_percentage_table_columns(),
208            contiguous_text_runs: Vec::new(),
209            jobs: Vec::new(),
210            has_text_runs: false,
211        }
212    }
213
214    fn wrap_any_text_in_anonymous_block_container(&mut self) {
215        let runs = std::mem::take(&mut self.contiguous_text_runs);
216        if runs
217            .iter()
218            .all(ModernContainerTextRun::is_only_document_white_space)
219        {
220            // There is no text run, or they all only contain document white space characters
221        } else {
222            self.jobs.push(ModernContainerJob::TextRuns(runs));
223            self.has_text_runs = true;
224        }
225    }
226
227    pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> {
228        self.wrap_any_text_in_anonymous_block_container();
229
230        let anonymous_info = LazyLock::new(|| {
231            self.info
232                .with_pseudo_element(self.context, PseudoElement::ServoAnonymousBox)
233                .expect("Should always be able to construct info for anonymous boxes.")
234        });
235
236        let jobs = std::mem::take(&mut self.jobs);
237        let mut children: Vec<_> = if self.context.use_rayon {
238            jobs.into_par_iter()
239                .filter_map(|job| job.finish(&self, &anonymous_info))
240                .collect()
241        } else {
242            jobs.into_iter()
243                .filter_map(|job| job.finish(&self, &anonymous_info))
244                .collect()
245        };
246
247        // https://drafts.csswg.org/css-flexbox/#order-modified-document-order
248        children.sort_by_key(|child| child.order);
249
250        children
251    }
252}