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                )?;
67
68                let block_formatting_context = BlockFormattingContext::from_block_container(
69                    BlockContainer::InlineFormattingContext(inline_formatting_context),
70                );
71                let info: &NodeAndStyleInfo = anonymous_info;
72                let formatting_context = IndependentFormattingContext::new(
73                    LayoutBoxBase::new(info.into(), info.style.clone()),
74                    IndependentFormattingContextContents::Flow(block_formatting_context),
75                );
76
77                Some(ModernItem {
78                    kind: ModernItemKind::InFlow(formatting_context),
79                    order: 0,
80                    box_slot: None,
81                })
82            },
83            ModernContainerJob::ElementOrPseudoElement {
84                info,
85                display,
86                contents,
87                box_slot,
88            } => {
89                let is_abspos = info.style.get_box().position.is_absolutely_positioned();
90                let order = if is_abspos {
91                    0
92                } else {
93                    info.style.clone_order()
94                };
95
96                if let Some(layout_box) = box_slot
97                    .take_layout_box_if_undamaged(info.damage)
98                    .and_then(|layout_box| match &layout_box {
99                        LayoutBox::FlexLevel(_) | LayoutBox::TaffyItemBox(_) => Some(layout_box),
100                        _ => None,
101                    })
102                {
103                    return Some(ModernItem {
104                        kind: ModernItemKind::ReusedBox(layout_box),
105                        order,
106                        box_slot: Some(box_slot),
107                    });
108                }
109
110                // Text decorations are not propagated to any out-of-flow descendants. In addition,
111                // absolutes don't affect the size of ancestors so it is fine to allow descendent
112                // tables to resolve percentage columns.
113                let propagated_data = match is_abspos {
114                    false => builder.propagated_data,
115                    true => PropagatedBoxTreeData::default(),
116                };
117
118                let formatting_context = IndependentFormattingContext::construct(
119                    builder.context,
120                    &info,
121                    display.display_inside(),
122                    contents,
123                    propagated_data,
124                );
125
126                let kind = if is_abspos {
127                    ModernItemKind::OutOfFlow(formatting_context)
128                } else {
129                    ModernItemKind::InFlow(formatting_context)
130                };
131                Some(ModernItem {
132                    kind,
133                    order,
134                    box_slot: Some(box_slot),
135                })
136            },
137        }
138    }
139}
140
141struct ModernContainerTextRun<'dom> {
142    info: NodeAndStyleInfo<'dom>,
143    text: Cow<'dom, str>,
144}
145
146impl ModernContainerTextRun<'_> {
147    /// <https://drafts.csswg.org/css-text/#white-space>
148    fn is_only_document_white_space(&self) -> bool {
149        // FIXME: is this the right definition? See
150        // https://github.com/w3c/csswg-drafts/issues/5146
151        // https://github.com/w3c/csswg-drafts/issues/5147
152        self.text
153            .bytes()
154            .all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
155    }
156}
157
158pub(crate) enum ModernItemKind {
159    InFlow(IndependentFormattingContext),
160    OutOfFlow(IndependentFormattingContext),
161    ReusedBox(LayoutBox),
162}
163
164pub(crate) struct ModernItem<'dom> {
165    pub kind: ModernItemKind,
166    pub order: i32,
167    pub box_slot: Option<BoxSlot<'dom>>,
168}
169
170impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> {
171    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
172        self.contiguous_text_runs.push(ModernContainerTextRun {
173            info: info.clone(),
174            text,
175        })
176    }
177
178    /// Or pseudo-element
179    fn handle_element(
180        &mut self,
181        info: &NodeAndStyleInfo<'dom>,
182        display: DisplayGeneratingBox,
183        contents: Contents,
184        box_slot: BoxSlot<'dom>,
185    ) {
186        self.wrap_any_text_in_anonymous_block_container();
187
188        self.jobs.push(ModernContainerJob::ElementOrPseudoElement {
189            info: info.clone(),
190            display,
191            contents,
192            box_slot,
193        })
194    }
195}
196
197impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
198    pub fn new(
199        context: &'a LayoutContext<'a>,
200        info: &'a NodeAndStyleInfo<'dom>,
201        propagated_data: PropagatedBoxTreeData,
202    ) -> Self {
203        ModernContainerBuilder {
204            context,
205            info,
206            propagated_data: propagated_data.disallowing_percentage_table_columns(),
207            contiguous_text_runs: Vec::new(),
208            jobs: Vec::new(),
209            has_text_runs: false,
210        }
211    }
212
213    fn wrap_any_text_in_anonymous_block_container(&mut self) {
214        let runs = std::mem::take(&mut self.contiguous_text_runs);
215        if runs
216            .iter()
217            .all(ModernContainerTextRun::is_only_document_white_space)
218        {
219            // There is no text run, or they all only contain document white space characters
220        } else {
221            self.jobs.push(ModernContainerJob::TextRuns(runs));
222            self.has_text_runs = true;
223        }
224    }
225
226    pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> {
227        self.wrap_any_text_in_anonymous_block_container();
228
229        let anonymous_info = LazyLock::new(|| {
230            self.info
231                .with_pseudo_element(self.context, PseudoElement::ServoAnonymousBox)
232                .expect("Should always be able to construct info for anonymous boxes.")
233        });
234
235        let jobs = std::mem::take(&mut self.jobs);
236        let mut children: Vec<_> = if self.context.use_rayon {
237            jobs.into_par_iter()
238                .filter_map(|job| job.finish(&self, &anonymous_info))
239                .collect()
240        } else {
241            jobs.into_iter()
242                .filter_map(|job| job.finish(&self, &anonymous_info))
243                .collect()
244        };
245
246        // https://drafts.csswg.org/css-flexbox/#order-modified-document-order
247        children.sort_by_key(|child| child.order);
248
249        children
250    }
251}