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