Skip to main content

layout/flow/
construct.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use layout_api::LayoutNode;
6use rayon::iter::{IntoParallelIterator, ParallelIterator};
7use servo_arc::Arc;
8use style::properties::ComputedValues;
9use style::properties::longhands::list_style_position::computed_value::T as ListStylePosition;
10use style::selector_parser::PseudoElement;
11use style::str::char_is_whitespace;
12use style::values::specified::box_::DisplayOutside as StyloDisplayOutside;
13
14use super::OutsideMarker;
15use super::inline::construct::InlineFormattingContextBuilder;
16use super::inline::inline_box::InlineBox;
17use super::inline::{InlineFormattingContext, SharedInlineStyles};
18use crate::PropagatedBoxTreeData;
19use crate::cell::ArcRefCell;
20use crate::context::LayoutContext;
21use crate::dom::{BoxSlot, LayoutBox, NodeExt};
22use crate::dom_traversal::{
23    BoxTreeString, Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem,
24    TraversalHandler,
25};
26use crate::flow::float::FloatBox;
27use crate::flow::same_formatting_context_block::SameFormattingContextBlock;
28use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
29use crate::formatting_contexts::{
30    IndependentFormattingContext, IndependentFormattingContextContents,
31};
32use crate::fragment_tree::FragmentFlags;
33use crate::layout_box_base::LayoutBoxBase;
34use crate::positioned::AbsolutelyPositionedBox;
35use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, DisplayOutside};
36use crate::table::{AnonymousTableContent, AnonymousTableContentElement, Table};
37
38impl BlockFormattingContext {
39    pub(crate) fn construct(
40        context: &LayoutContext,
41        info: &NodeAndStyleInfo<'_>,
42        contents: NonReplacedContents,
43        propagated_data: PropagatedBoxTreeData,
44        is_list_item: bool,
45    ) -> Self {
46        Self::from_block_container(BlockContainer::construct(
47            context,
48            info,
49            contents,
50            propagated_data,
51            is_list_item,
52        ))
53    }
54
55    pub(crate) fn from_block_container(contents: BlockContainer) -> Self {
56        let contains_floats = contents.contains_floats();
57        Self {
58            contents,
59            contains_floats,
60        }
61    }
62}
63
64struct BlockLevelJob<'dom> {
65    info: NodeAndStyleInfo<'dom>,
66    box_slot: BoxSlot<'dom>,
67    propagated_data: PropagatedBoxTreeData,
68    kind: BlockLevelCreator,
69}
70
71pub(crate) enum BlockLevelCreator {
72    SameFormattingContextBlock(IntermediateBlockContainer),
73    Independent {
74        display_inside: DisplayInside,
75        contents: Contents,
76    },
77    OutOfFlowAbsolutelyPositionedBox {
78        display_inside: DisplayInside,
79        contents: Contents,
80    },
81    OutOfFlowFloatBox {
82        display_inside: DisplayInside,
83        contents: Contents,
84    },
85    OutsideMarker {
86        list_item_style: Arc<ComputedValues>,
87        contents: Vec<PseudoElementContentItem>,
88    },
89    AnonymousTable {
90        table_block: ArcRefCell<BlockLevelBox>,
91    },
92}
93
94impl BlockLevelCreator {
95    pub(crate) fn new_for_inflow_block_level_element<'dom>(
96        info: &NodeAndStyleInfo<'dom>,
97        display_inside: DisplayInside,
98        contents: Contents,
99        propagated_data: PropagatedBoxTreeData,
100    ) -> Self {
101        match contents {
102            Contents::NonReplaced(contents) => match display_inside {
103                DisplayInside::Flow { is_list_item }
104                    // Fragment flags are just used to indicate whether the element is replaced or a widget,
105                    // and whether it's a body or root propagating its `overflow` to the viewport. We have
106                    // already checked that the former is not the case.
107                    // TODO(#39932): empty flags are wrong when propagating `overflow` to the viewport.
108                    if !info.style.establishes_block_formatting_context(
109                        FragmentFlags::empty()
110                    ) =>
111                {
112                    Self::SameFormattingContextBlock(
113                        IntermediateBlockContainer::Deferred {
114                            contents,
115                            propagated_data,
116                            is_list_item,
117                        },
118                    )
119                },
120                _ => Self::Independent {
121                    display_inside,
122                    contents: Contents::NonReplaced(contents),
123                },
124            },
125            Contents::Replaced(_) | Contents::Widget(_) => Self::Independent {
126                display_inside,
127                contents,
128            },
129        }
130    }
131}
132
133/// A block container that may still have to be constructed.
134///
135/// Represents either the inline formatting context of an anonymous block
136/// box or the yet-to-be-computed block container generated from the children
137/// of a given element.
138///
139/// Deferring allows using rayon’s `into_par_iter`.
140pub(crate) enum IntermediateBlockContainer {
141    InlineFormattingContext(BlockContainer),
142    Deferred {
143        contents: NonReplacedContents,
144        propagated_data: PropagatedBoxTreeData,
145        is_list_item: bool,
146    },
147}
148
149/// A builder for a block container.
150///
151/// This builder starts from the first child of a given DOM node
152/// and does a preorder traversal of all of its inclusive siblings.
153pub(crate) struct BlockContainerBuilder<'dom, 'style> {
154    context: &'style LayoutContext<'style>,
155
156    /// This NodeAndStyleInfo contains the root node, the corresponding pseudo
157    /// content designator, and the block container style.
158    info: &'style NodeAndStyleInfo<'dom>,
159
160    /// The list of block-level boxes to be built for the final block container.
161    ///
162    /// Contains all the block-level jobs we found traversing the tree
163    /// so far, if this is empty at the end of the traversal and the ongoing
164    /// inline formatting context is not empty, the block container establishes
165    /// an inline formatting context (see end of `build`).
166    ///
167    /// DOM nodes which represent block-level boxes are immediately pushed
168    /// to this list with their style without ever being traversed at this
169    /// point, instead we just move to their next sibling. If the DOM node
170    /// doesn't have a next sibling, we either reached the end of the container
171    /// root or there are ongoing inline-level boxes
172    /// (see `handle_block_level_element`).
173    block_level_boxes: Vec<BlockLevelJob<'dom>>,
174
175    /// Whether or not this builder has yet produced an inline formatting context
176    /// with the first formatted line:
177    /// <https://www.w3.org/TR/css-pseudo-4/#first-formatted-line>.
178    /// This is used for `text-indent` and `::first-letter`.
179    have_already_seen_first_formatted_line: bool,
180
181    /// The propagated data to use for BoxTree construction.
182    propagated_data: PropagatedBoxTreeData,
183
184    /// The [`InlineFormattingContextBuilder`] if we have encountered any inline items,
185    /// otherwise None.
186    ///
187    /// TODO: This can be `OnceCell` once `OnceCell::get_mut_or_init` is stabilized.
188    inline_formatting_context_builder: Option<InlineFormattingContextBuilder>,
189
190    /// The [`NodeAndStyleInfo`] to use for anonymous block boxes pushed to the list of
191    /// block-level boxes, lazily initialized.
192    anonymous_box_info: Option<NodeAndStyleInfo<'dom>>,
193
194    /// A collection of content that is being added to an anonymous table. This is
195    /// composed of any sequence of internal table elements or table captions that
196    /// are found outside of a table.
197    anonymous_table_content: Vec<AnonymousTableContent<'dom>>,
198
199    /// Any [`InlineFormattingContexts`] created need to know about the ongoing `display: contents`
200    /// ancestors that have been processed. This `Vec` allows passing those into new
201    /// [`InlineFormattingContext`]s that we create.
202    display_contents_shared_styles: Vec<SharedInlineStyles>,
203}
204
205impl BlockContainer {
206    pub fn construct(
207        context: &LayoutContext,
208        info: &NodeAndStyleInfo<'_>,
209        contents: NonReplacedContents,
210        propagated_data: PropagatedBoxTreeData,
211        is_list_item: bool,
212    ) -> BlockContainer {
213        let mut builder = BlockContainerBuilder::new(context, info, propagated_data);
214
215        if is_list_item &&
216            let Some((marker_info, marker_contents)) = crate::lists::make_marker(context, info)
217        {
218            match marker_info.style.get_list_style_position() {
219                ListStylePosition::Inside => {
220                    builder.handle_list_item_marker_inside(&marker_info, marker_contents)
221                },
222                ListStylePosition::Outside => builder.handle_list_item_marker_outside(
223                    &marker_info,
224                    marker_contents,
225                    info.style.clone(),
226                ),
227            }
228        }
229
230        contents.traverse(context, info, &mut builder);
231        builder.finish()
232    }
233}
234
235impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
236    pub(crate) fn new(
237        context: &'style LayoutContext,
238        info: &'style NodeAndStyleInfo<'dom>,
239        propagated_data: PropagatedBoxTreeData,
240    ) -> Self {
241        BlockContainerBuilder {
242            context,
243            info,
244            block_level_boxes: Vec::new(),
245            propagated_data,
246            have_already_seen_first_formatted_line: false,
247            anonymous_box_info: None,
248            anonymous_table_content: Vec::new(),
249            inline_formatting_context_builder: None,
250            display_contents_shared_styles: Vec::new(),
251        }
252    }
253
254    fn currently_processing_inline_box(&self) -> bool {
255        self.inline_formatting_context_builder
256            .as_ref()
257            .is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box)
258    }
259
260    fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder {
261        self.inline_formatting_context_builder
262            .get_or_insert_with(|| {
263                // ::first-letter must be on the first formatted line.
264                let should_process_first_letter = !self.have_already_seen_first_formatted_line;
265                let mut builder = InlineFormattingContextBuilder::new(
266                    self.info,
267                    self.context,
268                    should_process_first_letter,
269                );
270                for shared_inline_styles in self.display_contents_shared_styles.iter() {
271                    builder.enter_display_contents(shared_inline_styles.clone());
272                }
273                builder
274            })
275    }
276
277    fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> {
278        self.inline_formatting_context_builder.take()?.finish(
279            self.context,
280            !self.have_already_seen_first_formatted_line,
281            self.info.node.is_single_line_text_input(),
282            self.info.style.to_bidi_level(),
283        )
284    }
285
286    pub(crate) fn finish(mut self) -> BlockContainer {
287        debug_assert!(!self.currently_processing_inline_box());
288
289        self.finish_anonymous_table_if_needed();
290
291        if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() {
292            // There are two options here. This block was composed of both one or more inline formatting contexts
293            // and child blocks OR this block was a single inline formatting context. In the latter case, we
294            // just return the inline formatting context as the block itself.
295            if self.block_level_boxes.is_empty() {
296                return BlockContainer::InlineFormattingContext(inline_formatting_context);
297            }
298            self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
299        }
300
301        let context = self.context;
302        let block_level_boxes = if self
303            .context
304            .should_parallelize(self.block_level_boxes.len())
305        {
306            self.block_level_boxes
307                .into_par_iter()
308                .map(|block_level_job| block_level_job.finish(context))
309                .collect()
310        } else {
311            self.block_level_boxes
312                .into_iter()
313                .map(|block_level_job| block_level_job.finish(context))
314                .collect()
315        };
316
317        BlockContainer::BlockLevelBoxes(block_level_boxes)
318    }
319
320    fn finish_anonymous_table_if_needed(&mut self) {
321        if self.anonymous_table_content.is_empty() {
322            return;
323        }
324
325        // From https://drafts.csswg.org/css-tables/#fixup-algorithm:
326        //  > If the box’s parent is an inline, run-in, or ruby box (or any box that would perform
327        //  > inlinification of its children), then an inline-table box must be generated; otherwise
328        //  > it must be a table box.
329        //
330        // Note that text content in the inline formatting context isn't enough to force the
331        // creation of an inline table. It requires the parent to be an inline box.
332        let inline_table = self.currently_processing_inline_box();
333
334        let mut contents: Vec<AnonymousTableContent<'dom>> =
335            std::mem::take(&mut self.anonymous_table_content);
336        let last_element_index = contents
337            .iter()
338            .rposition(|content| matches!(content, AnonymousTableContent::Element { .. }))
339            .expect("Anonymous table contents should include some table-level element");
340        let trailing_contents = contents.split_off(last_element_index + 1);
341
342        let (table_info, ifc) = Table::construct_anonymous(
343            self.context,
344            self,
345            self.info,
346            contents,
347            self.propagated_data,
348        );
349
350        if inline_table {
351            self.ensure_inline_formatting_context_builder()
352                .push_atomic(|| ArcRefCell::new(ifc), None);
353        } else {
354            let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
355
356            if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context()
357            {
358                self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
359            }
360
361            let box_slot = table_info.node.box_slot();
362            self.block_level_boxes.push(BlockLevelJob {
363                info: table_info,
364                box_slot,
365                kind: BlockLevelCreator::AnonymousTable { table_block },
366                propagated_data: self.propagated_data,
367            });
368        }
369
370        // If the anonymous table contents end with trailing whitespace, that
371        // whitespace doesn't actually belong to the table. It should be processed outside
372        // ie become a space between the anonymous table and the rest of the block
373        // content. Anonymous tables are really only constructed around internal table
374        // elements and the whitespace between them, so this trailing whitespace should
375        // not be included.
376        //
377        // See https://drafts.csswg.org/css-tables/#fixup-algorithm sections "Remove
378        // irrelevant boxes" and "Generate missing parents."
379        for content in trailing_contents {
380            match content {
381                AnonymousTableContent::Text(info, text) => self.handle_text(&info, text),
382                AnonymousTableContent::EnterDisplayContents(styles) => {
383                    self.enter_display_contents(styles)
384                },
385                AnonymousTableContent::LeaveDisplayContents => self.leave_display_contents(),
386                AnonymousTableContent::Element { .. } => {
387                    unreachable!("All elements were placed inside the table")
388                },
389            }
390        }
391    }
392}
393
394impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> {
395    fn handle_element(
396        &mut self,
397        info: &NodeAndStyleInfo<'dom>,
398        display: DisplayGeneratingBox,
399        contents: Contents,
400        box_slot: BoxSlot<'dom>,
401    ) {
402        match display {
403            DisplayGeneratingBox::OutsideInside { outside, inside } => {
404                self.finish_anonymous_table_if_needed();
405
406                match outside {
407                    DisplayOutside::Inline => {
408                        self.handle_inline_level_element(info, inside, contents, box_slot)
409                    },
410                    DisplayOutside::Block => {
411                        let box_style = info.style.get_box();
412                        // Floats and abspos cause blockification, so they only happen in this case.
413                        // https://drafts.csswg.org/css2/visuren.html#dis-pos-flo
414                        if box_style.position.is_absolutely_positioned() {
415                            self.handle_absolutely_positioned_element(
416                                info, inside, contents, box_slot,
417                            )
418                        } else if box_style.float.is_floating() {
419                            self.handle_float_element(info, inside, contents, box_slot)
420                        } else {
421                            self.handle_block_level_element(info, inside, contents, box_slot)
422                        }
423                    },
424                };
425            },
426            DisplayGeneratingBox::LayoutInternal(_) => {
427                self.anonymous_table_content
428                    .push(AnonymousTableContent::Element(Box::new(
429                        AnonymousTableContentElement {
430                            info: info.clone(),
431                            display,
432                            contents,
433                            box_slot,
434                        },
435                    )));
436            },
437        }
438    }
439
440    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: BoxTreeString<'dom>) {
441        if text.is_empty() && !info.node.text_node_needs_caret_placeholder() {
442            return;
443        }
444
445        // If we are building an anonymous table ie this text directly followed internal
446        // table elements that did not have a `<table>` ancestor, then we forward all
447        // whitespace to the table builder.
448        if !self.anonymous_table_content.is_empty() && text.chars().all(char_is_whitespace) {
449            self.anonymous_table_content
450                .push(AnonymousTableContent::Text(info.clone(), text));
451            return;
452        } else {
453            self.finish_anonymous_table_if_needed();
454        }
455
456        self.ensure_inline_formatting_context_builder();
457        self.inline_formatting_context_builder
458            .as_mut()
459            .expect("Should be guaranteed by line above")
460            .push_text_with_possible_first_letter(text, info, self.info, self.context);
461    }
462
463    fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
464        if !self.anonymous_table_content.is_empty() {
465            self.anonymous_table_content
466                .push(AnonymousTableContent::EnterDisplayContents(styles));
467            return;
468        }
469        self.display_contents_shared_styles.push(styles.clone());
470        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
471            builder.enter_display_contents(styles);
472        }
473    }
474
475    fn leave_display_contents(&mut self) {
476        if !self.anonymous_table_content.is_empty() {
477            self.anonymous_table_content
478                .push(AnonymousTableContent::LeaveDisplayContents);
479            return;
480        }
481        self.display_contents_shared_styles.pop();
482        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
483            builder.leave_display_contents();
484        }
485    }
486}
487
488impl<'dom> BlockContainerBuilder<'dom, '_> {
489    fn handle_list_item_marker_inside(
490        &mut self,
491        marker_info: &NodeAndStyleInfo<'dom>,
492        contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
493    ) {
494        let box_slot = marker_info.node.box_slot();
495        self.handle_inline_level_element(
496            marker_info,
497            DisplayInside::Flow {
498                is_list_item: false,
499            },
500            Contents::for_pseudo_element(contents),
501            box_slot,
502        );
503    }
504
505    fn handle_list_item_marker_outside(
506        &mut self,
507        marker_info: &NodeAndStyleInfo<'dom>,
508        contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
509        list_item_style: Arc<ComputedValues>,
510    ) {
511        let box_slot = marker_info.node.box_slot();
512        self.block_level_boxes.push(BlockLevelJob {
513            info: marker_info.clone(),
514            box_slot,
515            kind: BlockLevelCreator::OutsideMarker {
516                contents,
517                list_item_style,
518            },
519            propagated_data: self.propagated_data,
520        });
521    }
522
523    fn handle_inline_level_element(
524        &mut self,
525        info: &NodeAndStyleInfo<'dom>,
526        display_inside: DisplayInside,
527        contents: Contents,
528        box_slot: BoxSlot<'dom>,
529    ) {
530        let context = self.context;
531        let old_layout_box = box_slot.take_layout_box();
532        let (is_list_item, non_replaced_contents) = match (display_inside, contents) {
533            (
534                DisplayInside::Flow { is_list_item },
535                Contents::NonReplaced(non_replaced_contents),
536            ) => (is_list_item, non_replaced_contents),
537            (_, contents) => {
538                // If this inline element is an atomic, handle it and return.
539                let propagated_data = self.propagated_data;
540
541                let construction_callback = || {
542                    ArcRefCell::new(IndependentFormattingContext::construct(
543                        context,
544                        info,
545                        display_inside,
546                        contents,
547                        propagated_data,
548                    ))
549                };
550
551                let atomic = self
552                    .ensure_inline_formatting_context_builder()
553                    .push_atomic(construction_callback, old_layout_box);
554                box_slot.set(LayoutBox::InlineLevel(atomic));
555                return;
556            },
557        };
558
559        // Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
560        // before recurring is to remember this ongoing inline level box.
561        let inline_builder = self.ensure_inline_formatting_context_builder();
562        let inline_item = inline_builder.start_inline_box(
563            || ArcRefCell::new(InlineBox::new(info, context)),
564            old_layout_box,
565        );
566        box_slot.set(LayoutBox::InlineLevel(inline_item));
567
568        if info.node.is_html_wbr_element() {
569            inline_builder.push_soft_wrap_opportunity();
570        }
571
572        if is_list_item &&
573            let Some((marker_info, marker_contents)) =
574                crate::lists::make_marker(self.context, info)
575        {
576            // Ignore `list-style-position` here:
577            // “If the list item is an inline box: this value is equivalent to `inside`.”
578            // https://drafts.csswg.org/css-lists/#list-style-position-outside
579            self.handle_list_item_marker_inside(&marker_info, marker_contents)
580        }
581
582        non_replaced_contents.traverse(self.context, info, self);
583
584        self.finish_anonymous_table_if_needed();
585
586        self.inline_formatting_context_builder
587            .as_mut()
588            .expect("Should be building an InlineFormattingContext")
589            .end_inline_box();
590    }
591
592    fn handle_block_level_element(
593        &mut self,
594        info: &NodeAndStyleInfo<'dom>,
595        display_inside: DisplayInside,
596        contents: Contents,
597        box_slot: BoxSlot<'dom>,
598    ) {
599        let propagated_data = self.propagated_data;
600        let kind = BlockLevelCreator::new_for_inflow_block_level_element(
601            info,
602            display_inside,
603            contents,
604            propagated_data,
605        );
606        let job = BlockLevelJob {
607            info: info.clone(),
608            box_slot,
609            kind,
610            propagated_data,
611        };
612        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
613            if builder.currently_processing_inline_box() {
614                builder.push_block_level_box(job.finish(self.context));
615                return;
616            }
617            if let Some(context) = self.finish_ongoing_inline_formatting_context() {
618                self.push_block_level_job_for_inline_formatting_context(context);
619            }
620        }
621        self.block_level_boxes.push(job);
622
623        // Any block also counts as the first line for the purposes of text indent. Even if
624        // they don't actually indent.
625        self.have_already_seen_first_formatted_line = true;
626    }
627
628    fn handle_absolutely_positioned_element(
629        &mut self,
630        info: &NodeAndStyleInfo<'dom>,
631        display_inside: DisplayInside,
632        contents: Contents,
633        box_slot: BoxSlot<'dom>,
634    ) {
635        // If the original display was inline-level, then we need an inline formatting context
636        // in order to compute the static position correctly.
637        // If it was block-level, we don't want to break an existing inline formatting context,
638        // so push it there (`LineItemLayout::layout_absolute` can handle this well). But if
639        // there is no inline formatting context, then we can avoid creating one.
640        let needs_inline_builder =
641            info.style.get_box().original_display.outside() == StyloDisplayOutside::Inline;
642        if needs_inline_builder {
643            self.ensure_inline_formatting_context_builder();
644        }
645        let inline_builder = self
646            .inline_formatting_context_builder
647            .as_mut()
648            .filter(|builder| needs_inline_builder || !builder.is_empty);
649        if let Some(inline_builder) = inline_builder {
650            let constructor = || {
651                ArcRefCell::new(AbsolutelyPositionedBox::construct(
652                    self.context,
653                    info,
654                    display_inside,
655                    contents,
656                ))
657            };
658            let old_layout_box = box_slot.take_layout_box();
659            let inline_level_box =
660                inline_builder.push_absolutely_positioned_box(constructor, old_layout_box);
661            box_slot.set(LayoutBox::InlineLevel(inline_level_box));
662            return;
663        }
664
665        let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
666            contents,
667            display_inside,
668        };
669        self.block_level_boxes.push(BlockLevelJob {
670            info: info.clone(),
671            box_slot,
672            kind,
673            propagated_data: self.propagated_data,
674        });
675    }
676
677    fn handle_float_element(
678        &mut self,
679        info: &NodeAndStyleInfo<'dom>,
680        display_inside: DisplayInside,
681        contents: Contents,
682        box_slot: BoxSlot<'dom>,
683    ) {
684        if let Some(builder) = self.inline_formatting_context_builder.as_mut() &&
685            !builder.is_empty
686        {
687            let constructor = || {
688                ArcRefCell::new(FloatBox::construct(
689                    self.context,
690                    info,
691                    display_inside,
692                    contents,
693                    self.propagated_data,
694                ))
695            };
696            let old_layout_box = box_slot.take_layout_box();
697            let inline_level_box = builder.push_float_box(constructor, old_layout_box);
698            box_slot.set(LayoutBox::InlineLevel(inline_level_box));
699            return;
700        }
701
702        let kind = BlockLevelCreator::OutOfFlowFloatBox {
703            contents,
704            display_inside,
705        };
706        self.block_level_boxes.push(BlockLevelJob {
707            info: info.clone(),
708            box_slot,
709            kind,
710            propagated_data: self.propagated_data,
711        });
712    }
713
714    fn push_block_level_job_for_inline_formatting_context(
715        &mut self,
716        inline_formatting_context: InlineFormattingContext,
717    ) {
718        let layout_context = self.context;
719        let anonymous_info = self
720            .anonymous_box_info
721            .get_or_insert_with(|| {
722                self.info
723                    .with_pseudo_element(layout_context, PseudoElement::ServoAnonymousBox)
724                    .expect("Should never fail to create anonymous box")
725            })
726            .clone();
727
728        let box_slot = anonymous_info.node.box_slot();
729        self.block_level_boxes.push(BlockLevelJob {
730            info: anonymous_info,
731            box_slot,
732            kind: BlockLevelCreator::SameFormattingContextBlock(
733                IntermediateBlockContainer::InlineFormattingContext(
734                    BlockContainer::InlineFormattingContext(inline_formatting_context),
735                ),
736            ),
737            propagated_data: self.propagated_data,
738        });
739
740        self.have_already_seen_first_formatted_line = true;
741    }
742}
743
744impl BlockLevelJob<'_> {
745    fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
746        let info = &self.info;
747
748        // If this `BlockLevelBox` exists, it has been laid out before and is
749        // reusable.
750        if let Some(block_level_box) = match &*self.box_slot.slot.borrow() {
751            Some(LayoutBox::BlockLevel(block_level_box)) => Some(block_level_box.clone()),
752            _ => None,
753        } {
754            return block_level_box;
755        }
756
757        let block_level_box = match self.kind {
758            BlockLevelCreator::SameFormattingContextBlock(intermediate_block_container) => {
759                let contents = intermediate_block_container.finish(context, info);
760                let contains_floats = contents.contains_floats();
761
762                let base = LayoutBoxBase::new(info.into(), info.style.clone());
763                base.set_subtree_size(contents.subtree_size() + 1);
764
765                ArcRefCell::new(BlockLevelBox::SameFormattingContextBlock(
766                    SameFormattingContextBlock::new(base, contents, contains_floats),
767                ))
768            },
769            BlockLevelCreator::Independent {
770                display_inside,
771                contents,
772            } => {
773                let context = IndependentFormattingContext::construct(
774                    context,
775                    info,
776                    display_inside,
777                    contents,
778                    self.propagated_data,
779                );
780                ArcRefCell::new(BlockLevelBox::Independent(context))
781            },
782            BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
783                display_inside,
784                contents,
785            } => ArcRefCell::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
786                ArcRefCell::new(AbsolutelyPositionedBox::construct(
787                    context,
788                    info,
789                    display_inside,
790                    contents,
791                )),
792            )),
793            BlockLevelCreator::OutOfFlowFloatBox {
794                display_inside,
795                contents,
796            } => ArcRefCell::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
797                context,
798                info,
799                display_inside,
800                contents,
801                self.propagated_data,
802            ))),
803            BlockLevelCreator::OutsideMarker {
804                contents,
805                list_item_style,
806            } => {
807                let contents = NonReplacedContents::OfPseudoElement(contents);
808                let block_container = BlockContainer::construct(
809                    context,
810                    info,
811                    contents,
812                    self.propagated_data,
813                    false, /* is_list_item */
814                );
815                // An outside ::marker must establish a BFC, and can't contain floats.
816                let block_formatting_context = BlockFormattingContext {
817                    contents: block_container,
818                    contains_floats: false,
819                };
820                ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker {
821                    context: IndependentFormattingContext::new(
822                        LayoutBoxBase::new(info.into(), info.style.clone()),
823                        IndependentFormattingContextContents::Flow(block_formatting_context),
824                        self.propagated_data,
825                    ),
826                    list_item_style,
827                }))
828            },
829            BlockLevelCreator::AnonymousTable { table_block } => table_block,
830        };
831        self.box_slot
832            .set(LayoutBox::BlockLevel(block_level_box.clone()));
833        block_level_box
834    }
835}
836
837impl IntermediateBlockContainer {
838    fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer {
839        match self {
840            IntermediateBlockContainer::Deferred {
841                contents,
842                propagated_data,
843                is_list_item,
844            } => BlockContainer::construct(context, info, contents, propagated_data, is_list_item),
845            IntermediateBlockContainer::InlineFormattingContext(block_container) => block_container,
846        }
847    }
848}