1use 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
25pub(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 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, false, 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 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 fn is_only_document_white_space(&self) -> bool {
150 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 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 } 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 children.sort_by_key(|child| child.order);
249
250 children
251 }
252}