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 = InlineFormattingContextBuilder::new(
60 builder.info,
61 builder.context,
62 false, );
64 let mut last_style_from_display_contents: Option<SharedInlineStyles> = None;
65 for flex_text_run in runs.into_iter() {
66 match (
67 last_style_from_display_contents.as_ref(),
68 flex_text_run.style_from_display_contents.as_ref(),
69 ) {
70 (None, None) => {},
71 (Some(old_style), Some(new_style)) if old_style.ptr_eq(new_style) => {},
72 _ => {
73 if last_style_from_display_contents.is_some() {
77 inline_formatting_context_builder.leave_display_contents();
78 }
79 if let Some(ref new_style) = flex_text_run.style_from_display_contents {
80 inline_formatting_context_builder
81 .enter_display_contents(new_style.clone());
82 }
83 },
84 }
85 last_style_from_display_contents = flex_text_run.style_from_display_contents;
86 inline_formatting_context_builder.push_text(
87 flex_text_run.text,
88 &flex_text_run.info,
89 flex_text_run.info.node.text_node_selection(),
90 );
91 }
92
93 let inline_formatting_context = inline_formatting_context_builder
94 .finish(
95 builder.context,
96 true, false, builder.info.style.to_bidi_level(),
99 )
100 .expect("Did not expect document white space only text runs");
101
102 let block_formatting_context = BlockFormattingContext::from_block_container(
103 BlockContainer::InlineFormattingContext(inline_formatting_context),
104 );
105
106 let info = builder.anonymous_info();
107 let formatting_context = IndependentFormattingContext::new(
108 LayoutBoxBase::new(info.into(), info.style.clone()),
109 IndependentFormattingContextContents::Flow(block_formatting_context),
110 Default::default(),
113 );
114
115 Some(ModernItem {
116 kind: ModernItemKind::InFlow(formatting_context),
117 order: 0,
118 box_slot,
119 })
120 },
121 ModernContainerJob::ElementOrPseudoElement {
122 info,
123 display,
124 contents,
125 box_slot,
126 } => {
127 let is_abspos = info.style.get_box().position.is_absolutely_positioned();
128 let order = if is_abspos {
129 0
130 } else {
131 info.style.slow_clone_order()
132 };
133
134 if let Some(layout_box) =
135 box_slot
136 .take_layout_box()
137 .and_then(|layout_box| match &layout_box {
138 LayoutBox::FlexLevel(_) | LayoutBox::TaffyItemBox(_) => {
139 Some(layout_box)
140 },
141 _ => None,
142 })
143 {
144 return Some(ModernItem {
145 kind: ModernItemKind::ReusedBox(layout_box),
146 order,
147 box_slot,
148 });
149 }
150
151 let propagated_data = match is_abspos {
155 false => builder.propagated_data,
156 true => PropagatedBoxTreeData::default(),
157 };
158
159 let formatting_context = IndependentFormattingContext::construct(
160 builder.context,
161 &info,
162 display.display_inside(),
163 contents,
164 propagated_data,
165 );
166
167 let kind = if is_abspos {
168 ModernItemKind::OutOfFlow(formatting_context)
169 } else {
170 ModernItemKind::InFlow(formatting_context)
171 };
172 Some(ModernItem {
173 kind,
174 order,
175 box_slot,
176 })
177 },
178 }
179 }
180}
181
182struct ModernContainerTextRun<'dom> {
183 info: NodeAndStyleInfo<'dom>,
184 text: BoxTreeString<'dom>,
185 style_from_display_contents: Option<SharedInlineStyles>,
186}
187
188impl ModernContainerTextRun<'_> {
189 fn is_only_document_white_space(&self) -> bool {
194 self.text
195 .bytes()
196 .all(|byte| InlineFormattingContextBuilder::is_document_white_space(byte.into()))
197 }
198}
199
200pub(crate) enum ModernItemKind {
201 InFlow(IndependentFormattingContext),
202 OutOfFlow(IndependentFormattingContext),
203 ReusedBox(LayoutBox),
204}
205
206pub(crate) struct ModernItem<'dom> {
207 pub kind: ModernItemKind,
208 pub order: i32,
209 pub box_slot: BoxSlot<'dom>,
210}
211
212impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> {
213 fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: BoxTreeString<'dom>) {
214 self.contiguous_text_runs.push(ModernContainerTextRun {
215 info: info.clone(),
216 text,
217 style_from_display_contents: self.display_contents_shared_styles.last().cloned(),
218 })
219 }
220
221 fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
222 self.display_contents_shared_styles.push(styles);
223 }
224
225 fn leave_display_contents(&mut self) {
226 self.display_contents_shared_styles.pop();
227 }
228
229 fn handle_element(
231 &mut self,
232 info: &NodeAndStyleInfo<'dom>,
233 display: DisplayGeneratingBox,
234 contents: Contents,
235 box_slot: BoxSlot<'dom>,
236 ) {
237 self.wrap_any_text_in_anonymous_block_container();
238
239 self.jobs.push(ModernContainerJob::ElementOrPseudoElement {
240 info: info.clone(),
241 display,
242 contents,
243 box_slot,
244 })
245 }
246}
247
248impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
249 pub fn new(
250 context: &'a LayoutContext<'a>,
251 info: &'a NodeAndStyleInfo<'dom>,
252 propagated_data: PropagatedBoxTreeData,
253 ) -> Self {
254 ModernContainerBuilder {
255 context,
256 info,
257 anonymous_info: Default::default(),
258 propagated_data: propagated_data.disallowing_percentage_table_columns(),
259 contiguous_text_runs: Vec::new(),
260 jobs: Vec::new(),
261 has_text_runs: false,
262 display_contents_shared_styles: Vec::new(),
263 }
264 }
265
266 fn anonymous_info(&self) -> &NodeAndStyleInfo<'dom> {
267 self.anonymous_info.get_or_init(|| {
268 self.info
269 .with_pseudo_element(self.context, PseudoElement::ServoAnonymousBox)
270 .expect("Should always be able to construct info for anonymous boxes.")
271 })
272 }
273
274 fn wrap_any_text_in_anonymous_block_container(&mut self) {
275 let runs = std::mem::take(&mut self.contiguous_text_runs);
276
277 if runs
280 .iter()
281 .all(ModernContainerTextRun::is_only_document_white_space)
282 {
283 return;
284 }
285
286 let box_slot = self.anonymous_info().node.box_slot();
287 self.jobs.push(ModernContainerJob::TextRuns(runs, box_slot));
288 self.has_text_runs = true;
289 }
290
291 pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> {
292 self.wrap_any_text_in_anonymous_block_container();
293
294 let jobs = std::mem::take(&mut self.jobs);
295 let mut children: Vec<_> = if self.context.should_parallelize(jobs.iter().len()) {
296 jobs.into_par_iter()
297 .filter_map(|job| job.finish(&self))
298 .collect()
299 } else {
300 jobs.into_iter()
301 .filter_map(|job| job.finish(&self))
302 .collect()
303 };
304
305 children.sort_by_key(|child| child.order);
307
308 children
309 }
310}