1use std::sync::OnceLock;
8
9use layout_api::LayoutNode;
10use rayon::iter::{IntoParallelIterator, ParallelIterator};
11use style::selector_parser::PseudoElement;
12
13use crate::PropagatedBoxTreeData;
14use crate::context::LayoutContext;
15use crate::dom::{BoxSlot, LayoutBox, NodeExt};
16use crate::dom_traversal::{BoxTreeString, Contents, NodeAndStyleInfo, TraversalHandler};
17use crate::flow::inline::SharedInlineStyles;
18use crate::flow::inline::construct::InlineFormattingContextBuilder;
19use crate::flow::{BlockContainer, BlockFormattingContext};
20use crate::formatting_contexts::{
21 IndependentFormattingContext, IndependentFormattingContextContents,
22};
23use crate::layout_box_base::LayoutBoxBase;
24use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox};
25
26pub(crate) struct ModernContainerBuilder<'a, 'dom> {
28 context: &'a LayoutContext<'a>,
29 info: &'a NodeAndStyleInfo<'dom>,
30 anonymous_info: OnceLock<NodeAndStyleInfo<'dom>>,
33 propagated_data: PropagatedBoxTreeData,
34 contiguous_text_runs: Vec<ModernContainerTextRun<'dom>>,
35 jobs: Vec<ModernContainerJob<'dom>>,
37 has_text_runs: bool,
38 display_contents_shared_styles: Vec<SharedInlineStyles>,
43}
44
45enum ModernContainerJob<'dom> {
46 ElementOrPseudoElement {
47 info: NodeAndStyleInfo<'dom>,
48 display: DisplayGeneratingBox,
49 contents: Contents,
50 box_slot: BoxSlot<'dom>,
51 },
52 TextRuns(Vec<ModernContainerTextRun<'dom>>, BoxSlot<'dom>),
53}
54
55impl<'dom> ModernContainerJob<'dom> {
56 fn finish(self, builder: &ModernContainerBuilder) -> Option<ModernItem<'dom>> {
57 match self {
58 ModernContainerJob::TextRuns(runs, box_slot) => {
59 let mut inline_formatting_context_builder =
60 InlineFormattingContextBuilder::new(builder.info, builder.context);
61 let mut last_style_from_display_contents: Option<SharedInlineStyles> = None;
62 for flex_text_run in runs.into_iter() {
63 match (
64 last_style_from_display_contents.as_ref(),
65 flex_text_run.style_from_display_contents.as_ref(),
66 ) {
67 (None, None) => {},
68 (Some(old_style), Some(new_style)) if old_style.ptr_eq(new_style) => {},
69 _ => {
70 if last_style_from_display_contents.is_some() {
74 inline_formatting_context_builder.leave_display_contents();
75 }
76 if let Some(ref new_style) = flex_text_run.style_from_display_contents {
77 inline_formatting_context_builder
78 .enter_display_contents(new_style.clone());
79 }
80 },
81 }
82 last_style_from_display_contents = flex_text_run.style_from_display_contents;
83 inline_formatting_context_builder.push_text(
84 flex_text_run.text,
85 &flex_text_run.info,
86 flex_text_run.info.node.document_selection_in_text_node(),
87 );
88 }
89
90 let inline_formatting_context = inline_formatting_context_builder
91 .finish(
92 builder.context,
93 true, false, builder.info.style.to_bidi_level(),
96 )
97 .expect("Did not expect document white space only text runs");
98
99 let block_formatting_context = BlockFormattingContext::from_block_container(
100 BlockContainer::InlineFormattingContext(inline_formatting_context),
101 );
102
103 let info = builder.anonymous_info();
104 let formatting_context = IndependentFormattingContext::new(
105 LayoutBoxBase::new(info.into(), info.style.clone()),
106 IndependentFormattingContextContents::Flow(block_formatting_context),
107 Default::default(),
110 );
111
112 Some(ModernItem {
113 kind: ModernItemKind::InFlow(formatting_context),
114 order: 0,
115 box_slot,
116 })
117 },
118 ModernContainerJob::ElementOrPseudoElement {
119 info,
120 display,
121 contents,
122 box_slot,
123 } => {
124 let is_abspos = info.style.get_box().position.is_absolutely_positioned();
125 let order = if is_abspos {
126 0
127 } else {
128 info.style.clone_order()
129 };
130
131 if let Some(layout_box) =
132 box_slot
133 .take_layout_box()
134 .and_then(|layout_box| match &layout_box {
135 LayoutBox::FlexLevel(_) | LayoutBox::TaffyItemBox(_) => {
136 Some(layout_box)
137 },
138 _ => None,
139 })
140 {
141 return Some(ModernItem {
142 kind: ModernItemKind::ReusedBox(layout_box),
143 order,
144 box_slot,
145 });
146 }
147
148 let propagated_data = match is_abspos {
152 false => builder.propagated_data,
153 true => PropagatedBoxTreeData::default(),
154 };
155
156 let formatting_context = IndependentFormattingContext::construct(
157 builder.context,
158 &info,
159 display.display_inside(),
160 contents,
161 propagated_data,
162 );
163
164 let kind = if is_abspos {
165 ModernItemKind::OutOfFlow(formatting_context)
166 } else {
167 ModernItemKind::InFlow(formatting_context)
168 };
169 Some(ModernItem {
170 kind,
171 order,
172 box_slot,
173 })
174 },
175 }
176 }
177}
178
179struct ModernContainerTextRun<'dom> {
180 info: NodeAndStyleInfo<'dom>,
181 text: BoxTreeString<'dom>,
182 style_from_display_contents: Option<SharedInlineStyles>,
183}
184
185impl ModernContainerTextRun<'_> {
186 fn is_only_document_white_space(&self) -> bool {
191 self.text
192 .bytes()
193 .all(|byte| InlineFormattingContextBuilder::is_document_white_space(byte.into()))
194 }
195}
196
197pub(crate) enum ModernItemKind {
198 InFlow(IndependentFormattingContext),
199 OutOfFlow(IndependentFormattingContext),
200 ReusedBox(LayoutBox),
201}
202
203pub(crate) struct ModernItem<'dom> {
204 pub kind: ModernItemKind,
205 pub order: i32,
206 pub box_slot: BoxSlot<'dom>,
207}
208
209impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> {
210 fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: BoxTreeString<'dom>) {
211 self.contiguous_text_runs.push(ModernContainerTextRun {
212 info: info.clone(),
213 text,
214 style_from_display_contents: self.display_contents_shared_styles.last().cloned(),
215 })
216 }
217
218 fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
219 self.display_contents_shared_styles.push(styles);
220 }
221
222 fn leave_display_contents(&mut self) {
223 self.display_contents_shared_styles.pop();
224 }
225
226 fn handle_element(
228 &mut self,
229 info: &NodeAndStyleInfo<'dom>,
230 display: DisplayGeneratingBox,
231 contents: Contents,
232 box_slot: BoxSlot<'dom>,
233 ) {
234 self.wrap_any_text_in_anonymous_block_container();
235
236 self.jobs.push(ModernContainerJob::ElementOrPseudoElement {
237 info: info.clone(),
238 display,
239 contents,
240 box_slot,
241 })
242 }
243}
244
245impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
246 pub fn new(
247 context: &'a LayoutContext<'a>,
248 info: &'a NodeAndStyleInfo<'dom>,
249 propagated_data: PropagatedBoxTreeData,
250 ) -> Self {
251 ModernContainerBuilder {
252 context,
253 info,
254 anonymous_info: Default::default(),
255 propagated_data: propagated_data.disallowing_percentage_table_columns(),
256 contiguous_text_runs: Vec::new(),
257 jobs: Vec::new(),
258 has_text_runs: false,
259 display_contents_shared_styles: Vec::new(),
260 }
261 }
262
263 fn anonymous_info(&self) -> &NodeAndStyleInfo<'dom> {
264 self.anonymous_info.get_or_init(|| {
265 self.info
266 .with_pseudo_element(self.context, PseudoElement::ServoAnonymousBox)
267 .expect("Should always be able to construct info for anonymous boxes.")
268 })
269 }
270
271 fn wrap_any_text_in_anonymous_block_container(&mut self) {
272 let runs = std::mem::take(&mut self.contiguous_text_runs);
273
274 if runs
277 .iter()
278 .all(ModernContainerTextRun::is_only_document_white_space)
279 {
280 return;
281 }
282
283 let box_slot = self.anonymous_info().node.box_slot();
284 self.jobs.push(ModernContainerJob::TextRuns(runs, box_slot));
285 self.has_text_runs = true;
286 }
287
288 pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> {
289 self.wrap_any_text_in_anonymous_block_container();
290
291 let jobs = std::mem::take(&mut self.jobs);
292 let mut children: Vec<_> = if self.context.should_parallelize(jobs.iter().len()) {
293 jobs.into_par_iter()
294 .filter_map(|job| job.finish(&self))
295 .collect()
296 } else {
297 jobs.into_iter()
298 .filter_map(|job| job.finish(&self))
299 .collect()
300 };
301
302 children.sort_by_key(|child| child.order);
304
305 children
306 }
307}