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 )?;
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 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 fn is_only_document_white_space(&self) -> bool {
149 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 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 } 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 children.sort_by_key(|child| child.order);
248
249 children
250 }
251}