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 std::borrow::Cow;
6
7use rayon::iter::{IntoParallelIterator, ParallelIterator};
8use servo_arc::Arc;
9use style::properties::ComputedValues;
10use style::properties::longhands::list_style_position::computed_value::T as ListStylePosition;
11use style::selector_parser::PseudoElement;
12use style::str::char_is_whitespace;
13use style::values::specified::box_::DisplayOutside as StyloDisplayOutside;
14use unicode_categories::UnicodeCategories;
15
16use super::OutsideMarker;
17use super::inline::construct::InlineFormattingContextBuilder;
18use super::inline::inline_box::InlineBox;
19use super::inline::{InlineFormattingContext, SharedInlineStyles};
20use crate::PropagatedBoxTreeData;
21use crate::cell::ArcRefCell;
22use crate::context::LayoutContext;
23use crate::dom::{BoxSlot, LayoutBox, NodeExt};
24use crate::dom_traversal::{
25    Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem, TraversalHandler,
26};
27use crate::flow::float::FloatBox;
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, 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 a block which would be
176    /// be considered the first line for the purposes of `text-indent`.
177    have_already_seen_first_line_for_text_indent: bool,
178
179    /// The propagated data to use for BoxTree construction.
180    propagated_data: PropagatedBoxTreeData,
181
182    /// The [`InlineFormattingContextBuilder`] if we have encountered any inline items,
183    /// otherwise None.
184    ///
185    /// TODO: This can be `OnceCell` once `OnceCell::get_mut_or_init` is stabilized.
186    inline_formatting_context_builder: Option<InlineFormattingContextBuilder>,
187
188    /// The [`NodeAndStyleInfo`] to use for anonymous block boxes pushed to the list of
189    /// block-level boxes, lazily initialized.
190    anonymous_box_info: Option<NodeAndStyleInfo<'dom>>,
191
192    /// A collection of content that is being added to an anonymous table. This is
193    /// composed of any sequence of internal table elements or table captions that
194    /// are found outside of a table.
195    anonymous_table_content: Vec<AnonymousTableContent<'dom>>,
196
197    /// Any [`InlineFormattingContexts`] created need to know about the ongoing `display: contents`
198    /// ancestors that have been processed. This `Vec` allows passing those into new
199    /// [`InlineFormattingContext`]s that we create.
200    display_contents_shared_styles: Vec<SharedInlineStyles>,
201}
202
203impl BlockContainer {
204    pub fn construct(
205        context: &LayoutContext,
206        info: &NodeAndStyleInfo<'_>,
207        contents: NonReplacedContents,
208        propagated_data: PropagatedBoxTreeData,
209        is_list_item: bool,
210    ) -> BlockContainer {
211        let mut builder = BlockContainerBuilder::new(context, info, propagated_data);
212
213        if is_list_item {
214            if let Some((marker_info, marker_contents)) = crate::lists::make_marker(context, info) {
215                match marker_info.style.clone_list_style_position() {
216                    ListStylePosition::Inside => {
217                        builder.handle_list_item_marker_inside(&marker_info, marker_contents)
218                    },
219                    ListStylePosition::Outside => builder.handle_list_item_marker_outside(
220                        &marker_info,
221                        marker_contents,
222                        info.style.clone(),
223                    ),
224                }
225            }
226        }
227
228        contents.traverse(context, info, &mut builder);
229        builder.finish()
230    }
231}
232
233impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
234    pub(crate) fn new(
235        context: &'style LayoutContext,
236        info: &'style NodeAndStyleInfo<'dom>,
237        propagated_data: PropagatedBoxTreeData,
238    ) -> Self {
239        BlockContainerBuilder {
240            context,
241            info,
242            block_level_boxes: Vec::new(),
243            propagated_data,
244            have_already_seen_first_line_for_text_indent: false,
245            anonymous_box_info: None,
246            anonymous_table_content: Vec::new(),
247            inline_formatting_context_builder: None,
248            display_contents_shared_styles: Vec::new(),
249        }
250    }
251
252    fn currently_processing_inline_box(&self) -> bool {
253        self.inline_formatting_context_builder
254            .as_ref()
255            .is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box)
256    }
257
258    fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder {
259        self.inline_formatting_context_builder
260            .get_or_insert_with(|| {
261                let mut builder = InlineFormattingContextBuilder::new(self.info, self.context);
262                for shared_inline_styles in self.display_contents_shared_styles.iter() {
263                    builder.enter_display_contents(shared_inline_styles.clone());
264                }
265                builder
266            })
267    }
268
269    fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> {
270        self.inline_formatting_context_builder.take()?.finish(
271            self.context,
272            !self.have_already_seen_first_line_for_text_indent,
273            self.info.node.is_single_line_text_input(),
274            self.info.style.to_bidi_level(),
275        )
276    }
277
278    pub(crate) fn finish(mut self) -> BlockContainer {
279        debug_assert!(!self.currently_processing_inline_box());
280
281        self.finish_anonymous_table_if_needed();
282
283        if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() {
284            // There are two options here. This block was composed of both one or more inline formatting contexts
285            // and child blocks OR this block was a single inline formatting context. In the latter case, we
286            // just return the inline formatting context as the block itself.
287            if self.block_level_boxes.is_empty() {
288                return BlockContainer::InlineFormattingContext(inline_formatting_context);
289            }
290            self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
291        }
292
293        let context = self.context;
294        let block_level_boxes = if self.context.use_rayon {
295            self.block_level_boxes
296                .into_par_iter()
297                .map(|block_level_job| block_level_job.finish(context))
298                .collect()
299        } else {
300            self.block_level_boxes
301                .into_iter()
302                .map(|block_level_job| block_level_job.finish(context))
303                .collect()
304        };
305
306        BlockContainer::BlockLevelBoxes(block_level_boxes)
307    }
308
309    fn finish_anonymous_table_if_needed(&mut self) {
310        if self.anonymous_table_content.is_empty() {
311            return;
312        }
313
314        // From https://drafts.csswg.org/css-tables/#fixup-algorithm:
315        //  > If the box’s parent is an inline, run-in, or ruby box (or any box that would perform
316        //  > inlinification of its children), then an inline-table box must be generated; otherwise
317        //  > it must be a table box.
318        //
319        // Note that text content in the inline formatting context isn't enough to force the
320        // creation of an inline table. It requires the parent to be an inline box.
321        let inline_table = self.currently_processing_inline_box();
322
323        let contents: Vec<AnonymousTableContent<'dom>> =
324            self.anonymous_table_content.drain(..).collect();
325        let last_text = match contents.last() {
326            Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())),
327            _ => None,
328        };
329
330        let (table_info, ifc) =
331            Table::construct_anonymous(self.context, self.info, contents, self.propagated_data);
332
333        if inline_table {
334            self.ensure_inline_formatting_context_builder()
335                .push_atomic(|| ArcRefCell::new(ifc), None);
336        } else {
337            let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
338
339            if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context()
340            {
341                self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
342            }
343
344            let box_slot = table_info.node.box_slot();
345            self.block_level_boxes.push(BlockLevelJob {
346                info: table_info,
347                box_slot,
348                kind: BlockLevelCreator::AnonymousTable { table_block },
349                propagated_data: self.propagated_data,
350            });
351        }
352
353        // If the last element in the anonymous table content is whitespace, that
354        // whitespace doesn't actually belong to the table. It should be processed outside
355        // ie become a space between the anonymous table and the rest of the block
356        // content. Anonymous tables are really only constructed around internal table
357        // elements and the whitespace between them, so this trailing whitespace should
358        // not be included.
359        //
360        // See https://drafts.csswg.org/css-tables/#fixup-algorithm sections "Remove
361        // irrelevant boxes" and "Generate missing parents."
362        if let Some((info, text)) = last_text {
363            self.handle_text(&info, text);
364        }
365    }
366}
367
368/// Computes the range of the first letter.
369///
370/// The range includes any preceding punctuation, and any spaces interleaved
371/// within the preceding punctuation or between the preceding punctuation
372/// and the first letter/number/symbol.
373/// Succeeding punctuation are included in the range, but any space
374/// following the letter/number/symbol ends the range. Intervening
375/// succeeding spaces are not supported yet.
376///
377/// <https://drafts.csswg.org/css-pseudo/#first-letter-pattern>
378fn first_letter_range(text: &str) -> std::ops::Range<usize> {
379    use unicode_categories::UnicodeCategories;
380
381    enum State {
382        Start,
383        PrecedingPunc,
384        /// Unicode general category L: letter, N: number and S: symbol
385        Lns,
386        SucceedingPunc,
387    }
388
389    let mut start = 0;
390    let mut end = None;
391    let mut state = State::Start;
392
393    for (i, c) in text.char_indices() {
394        match &mut state {
395            State::Start => {
396                if c.is_punctuation() {
397                    start = i;
398                    state = State::PrecedingPunc;
399                } else if c.is_letter() || c.is_number() || c.is_symbol() {
400                    start = i;
401                    state = State::Lns;
402                } else if c.is_separator_space() {
403                    continue;
404                } else {
405                    // Found invalid character
406                    return 0..0;
407                }
408            },
409            State::PrecedingPunc => {
410                if c.is_letter() || c.is_number() || c.is_symbol() {
411                    state = State::Lns;
412                } else if c.is_punctuation() || (c.is_separator_space() && c != '\u{3000}') {
413                    continue;
414                } else {
415                    // Found invalid character
416                    return 0..0;
417                }
418            },
419            State::Lns => {
420                // TODO: Implement support for intervening spaces
421                // <https://drafts.csswg.org/css-pseudo/#first-letter-pattern>
422                if c.is_punctuation() && !c.is_punctuation_open() && !c.is_punctuation_dash() {
423                    state = State::SucceedingPunc;
424                } else {
425                    end = Some(i);
426                    break;
427                }
428            },
429            State::SucceedingPunc => {
430                // TODO: Implement support for intervening spaces
431                // <https://drafts.csswg.org/css-pseudo/#first-letter-pattern>
432                if c.is_punctuation() && !c.is_punctuation_open() && !c.is_punctuation_dash() {
433                    continue;
434                } else {
435                    end = Some(i);
436                    break;
437                }
438            },
439        }
440    }
441
442    match state {
443        State::Start | State::PrecedingPunc => 0..0,
444        State::Lns | State::SucceedingPunc => {
445            let end = end.unwrap_or(text.len());
446            start..end
447        },
448    }
449}
450
451impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> {
452    fn handle_element(
453        &mut self,
454        info: &NodeAndStyleInfo<'dom>,
455        display: DisplayGeneratingBox,
456        contents: Contents,
457        box_slot: BoxSlot<'dom>,
458    ) {
459        match display {
460            DisplayGeneratingBox::OutsideInside { outside, inside } => {
461                self.finish_anonymous_table_if_needed();
462
463                match outside {
464                    DisplayOutside::Inline => {
465                        self.handle_inline_level_element(info, inside, contents, box_slot)
466                    },
467                    DisplayOutside::Block => {
468                        let box_style = info.style.get_box();
469                        // Floats and abspos cause blockification, so they only happen in this case.
470                        // https://drafts.csswg.org/css2/visuren.html#dis-pos-flo
471                        if box_style.position.is_absolutely_positioned() {
472                            self.handle_absolutely_positioned_element(
473                                info, inside, contents, box_slot,
474                            )
475                        } else if box_style.float.is_floating() {
476                            self.handle_float_element(info, inside, contents, box_slot)
477                        } else {
478                            self.handle_block_level_element(info, inside, contents, box_slot)
479                        }
480                    },
481                };
482            },
483            DisplayGeneratingBox::LayoutInternal(_) => {
484                self.anonymous_table_content
485                    .push(AnonymousTableContent::Element {
486                        info: info.clone(),
487                        display,
488                        contents,
489                        box_slot,
490                    });
491            },
492        }
493    }
494
495    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
496        if text.is_empty() {
497            return;
498        }
499
500        // If we are building an anonymous table ie this text directly followed internal
501        // table elements that did not have a `<table>` ancestor, then we forward all
502        // whitespace to the table builder.
503        if !self.anonymous_table_content.is_empty() && text.chars().all(char_is_whitespace) {
504            self.anonymous_table_content
505                .push(AnonymousTableContent::Text(info.clone(), text));
506            return;
507        } else {
508            self.finish_anonymous_table_if_needed();
509        }
510
511        let container_info = self.info;
512        let context = self.context;
513        let builder = self.ensure_inline_formatting_context_builder();
514
515        // ::first-letter is an eager pseudo element and should not be nested
516        if let Some(pseudo_info) = (container_info.pseudo_element_chain().is_empty())
517            .then(|| container_info.with_pseudo_element(context, PseudoElement::FirstLetter))
518            .flatten()
519            .filter(|_| {
520                builder
521                    .text_segments
522                    .iter()
523                    .flat_map(|seg| seg.chars())
524                    .all(|c| c.is_separator_space())
525            })
526        {
527            let first_letter_range = first_letter_range(&text[..]);
528
529            // The first letter range may be some value larger than zero when
530            // there are preceding spaces.
531            if first_letter_range.start != 0 {
532                builder.push_text(Cow::Borrowed(&text[0..first_letter_range.start]), info);
533            }
534
535            builder.start_inline_box(
536                || ArcRefCell::new(InlineBox::new(&pseudo_info, context)),
537                None,
538            );
539            let first_letter_text = Cow::Borrowed(&text[first_letter_range.clone()]);
540            builder.push_text(first_letter_text, &pseudo_info);
541            builder.end_inline_box();
542
543            builder.push_text(Cow::Borrowed(&text[first_letter_range.end..]), info);
544        } else {
545            builder.push_text(text, info);
546        }
547    }
548
549    fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
550        self.display_contents_shared_styles.push(styles.clone());
551        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
552            builder.enter_display_contents(styles);
553        }
554    }
555
556    fn leave_display_contents(&mut self) {
557        self.display_contents_shared_styles.pop();
558        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
559            builder.leave_display_contents();
560        }
561    }
562}
563
564impl<'dom> BlockContainerBuilder<'dom, '_> {
565    fn handle_list_item_marker_inside(
566        &mut self,
567        marker_info: &NodeAndStyleInfo<'dom>,
568        contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
569    ) {
570        let box_slot = marker_info.node.box_slot();
571        self.handle_inline_level_element(
572            marker_info,
573            DisplayInside::Flow {
574                is_list_item: false,
575            },
576            Contents::for_pseudo_element(contents),
577            box_slot,
578        );
579    }
580
581    fn handle_list_item_marker_outside(
582        &mut self,
583        marker_info: &NodeAndStyleInfo<'dom>,
584        contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
585        list_item_style: Arc<ComputedValues>,
586    ) {
587        let box_slot = marker_info.node.box_slot();
588        self.block_level_boxes.push(BlockLevelJob {
589            info: marker_info.clone(),
590            box_slot,
591            kind: BlockLevelCreator::OutsideMarker {
592                contents,
593                list_item_style,
594            },
595            propagated_data: self.propagated_data,
596        });
597    }
598
599    fn handle_inline_level_element(
600        &mut self,
601        info: &NodeAndStyleInfo<'dom>,
602        display_inside: DisplayInside,
603        contents: Contents,
604        box_slot: BoxSlot<'dom>,
605    ) {
606        let context = self.context;
607        let old_layout_box = box_slot.take_layout_box();
608        let (is_list_item, non_replaced_contents) = match (display_inside, contents) {
609            (
610                DisplayInside::Flow { is_list_item },
611                Contents::NonReplaced(non_replaced_contents),
612            ) => (is_list_item, non_replaced_contents),
613            (_, contents) => {
614                // If this inline element is an atomic, handle it and return.
615                let propagated_data = self.propagated_data;
616
617                let construction_callback = || {
618                    ArcRefCell::new(IndependentFormattingContext::construct(
619                        context,
620                        info,
621                        display_inside,
622                        contents,
623                        propagated_data,
624                    ))
625                };
626
627                let atomic = self
628                    .ensure_inline_formatting_context_builder()
629                    .push_atomic(construction_callback, old_layout_box);
630                box_slot.set(LayoutBox::InlineLevel(atomic));
631                return;
632            },
633        };
634
635        // Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
636        // before recurring is to remember this ongoing inline level box.
637        let inline_builder = self.ensure_inline_formatting_context_builder();
638        inline_builder.start_inline_box(
639            || ArcRefCell::new(InlineBox::new(info, context)),
640            old_layout_box,
641        );
642        box_slot.set(LayoutBox::InlineLevel(
643            inline_builder.inline_items.last().unwrap().clone(),
644        ));
645
646        if is_list_item {
647            if let Some((marker_info, marker_contents)) =
648                crate::lists::make_marker(self.context, info)
649            {
650                // Ignore `list-style-position` here:
651                // “If the list item is an inline box: this value is equivalent to `inside`.”
652                // https://drafts.csswg.org/css-lists/#list-style-position-outside
653                self.handle_list_item_marker_inside(&marker_info, marker_contents)
654            }
655        }
656
657        // `unwrap` doesn’t panic here because `is_replaced` returned `false`.
658        non_replaced_contents.traverse(self.context, info, self);
659
660        self.finish_anonymous_table_if_needed();
661
662        self.inline_formatting_context_builder
663            .as_mut()
664            .expect("Should be building an InlineFormattingContext")
665            .end_inline_box();
666    }
667
668    fn handle_block_level_element(
669        &mut self,
670        info: &NodeAndStyleInfo<'dom>,
671        display_inside: DisplayInside,
672        contents: Contents,
673        box_slot: BoxSlot<'dom>,
674    ) {
675        let propagated_data = self.propagated_data;
676        let kind = BlockLevelCreator::new_for_inflow_block_level_element(
677            info,
678            display_inside,
679            contents,
680            propagated_data,
681        );
682        let job = BlockLevelJob {
683            info: info.clone(),
684            box_slot,
685            kind,
686            propagated_data,
687        };
688        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
689            if builder.currently_processing_inline_box() {
690                builder.push_block_level_box(job.finish(self.context));
691                return;
692            }
693            if let Some(context) = self.finish_ongoing_inline_formatting_context() {
694                self.push_block_level_job_for_inline_formatting_context(context);
695            }
696        }
697        self.block_level_boxes.push(job);
698
699        // Any block also counts as the first line for the purposes of text indent. Even if
700        // they don't actually indent.
701        self.have_already_seen_first_line_for_text_indent = true;
702    }
703
704    fn handle_absolutely_positioned_element(
705        &mut self,
706        info: &NodeAndStyleInfo<'dom>,
707        display_inside: DisplayInside,
708        contents: Contents,
709        box_slot: BoxSlot<'dom>,
710    ) {
711        // If the original display was inline-level, then we need an inline formatting context
712        // in order to compute the static position correctly.
713        // If it was block-level, we don't want to break an existing inline formatting context,
714        // so push it there (`LineItemLayout::layout_absolute` can handle this well). But if
715        // there is no inline formatting context, then we can avoid creating one.
716        let needs_inline_builder =
717            info.style.get_box().original_display.outside() == StyloDisplayOutside::Inline;
718        if needs_inline_builder {
719            self.ensure_inline_formatting_context_builder();
720        }
721        let inline_builder = self
722            .inline_formatting_context_builder
723            .as_mut()
724            .filter(|builder| needs_inline_builder || !builder.is_empty);
725        if let Some(inline_builder) = inline_builder {
726            let constructor = || {
727                ArcRefCell::new(AbsolutelyPositionedBox::construct(
728                    self.context,
729                    info,
730                    display_inside,
731                    contents,
732                ))
733            };
734            let old_layout_box = box_slot.take_layout_box();
735            let inline_level_box =
736                inline_builder.push_absolutely_positioned_box(constructor, old_layout_box);
737            box_slot.set(LayoutBox::InlineLevel(inline_level_box));
738            return;
739        }
740
741        let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
742            contents,
743            display_inside,
744        };
745        self.block_level_boxes.push(BlockLevelJob {
746            info: info.clone(),
747            box_slot,
748            kind,
749            propagated_data: self.propagated_data,
750        });
751    }
752
753    fn handle_float_element(
754        &mut self,
755        info: &NodeAndStyleInfo<'dom>,
756        display_inside: DisplayInside,
757        contents: Contents,
758        box_slot: BoxSlot<'dom>,
759    ) {
760        if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
761            if !builder.is_empty {
762                let constructor = || {
763                    ArcRefCell::new(FloatBox::construct(
764                        self.context,
765                        info,
766                        display_inside,
767                        contents,
768                        self.propagated_data,
769                    ))
770                };
771                let old_layout_box = box_slot.take_layout_box();
772                let inline_level_box = builder.push_float_box(constructor, old_layout_box);
773                box_slot.set(LayoutBox::InlineLevel(inline_level_box));
774                return;
775            }
776        }
777
778        let kind = BlockLevelCreator::OutOfFlowFloatBox {
779            contents,
780            display_inside,
781        };
782        self.block_level_boxes.push(BlockLevelJob {
783            info: info.clone(),
784            box_slot,
785            kind,
786            propagated_data: self.propagated_data,
787        });
788    }
789
790    fn push_block_level_job_for_inline_formatting_context(
791        &mut self,
792        inline_formatting_context: InlineFormattingContext,
793    ) {
794        let layout_context = self.context;
795        let anonymous_info = self
796            .anonymous_box_info
797            .get_or_insert_with(|| {
798                self.info
799                    .with_pseudo_element(layout_context, PseudoElement::ServoAnonymousBox)
800                    .expect("Should never fail to create anonymous box")
801            })
802            .clone();
803
804        let box_slot = anonymous_info.node.box_slot();
805        self.block_level_boxes.push(BlockLevelJob {
806            info: anonymous_info,
807            box_slot,
808            kind: BlockLevelCreator::SameFormattingContextBlock(
809                IntermediateBlockContainer::InlineFormattingContext(
810                    BlockContainer::InlineFormattingContext(inline_formatting_context),
811                ),
812            ),
813            propagated_data: self.propagated_data,
814        });
815
816        self.have_already_seen_first_line_for_text_indent = true;
817    }
818}
819
820impl BlockLevelJob<'_> {
821    fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
822        let info = &self.info;
823
824        // If this `BlockLevelBox` exists, it has been laid out before and is
825        // reusable.
826        if let Some(block_level_box) = match &*self.box_slot.slot.borrow() {
827            Some(LayoutBox::BlockLevel(block_level_box)) => Some(block_level_box.clone()),
828            _ => None,
829        } {
830            return block_level_box;
831        }
832
833        let block_level_box = match self.kind {
834            BlockLevelCreator::SameFormattingContextBlock(intermediate_block_container) => {
835                let contents = intermediate_block_container.finish(context, info);
836                let contains_floats = contents.contains_floats();
837                ArcRefCell::new(BlockLevelBox::SameFormattingContextBlock {
838                    base: LayoutBoxBase::new(info.into(), info.style.clone()),
839                    contents,
840                    contains_floats,
841                })
842            },
843            BlockLevelCreator::Independent {
844                display_inside,
845                contents,
846            } => {
847                let context = IndependentFormattingContext::construct(
848                    context,
849                    info,
850                    display_inside,
851                    contents,
852                    self.propagated_data,
853                );
854                ArcRefCell::new(BlockLevelBox::Independent(context))
855            },
856            BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
857                display_inside,
858                contents,
859            } => ArcRefCell::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
860                ArcRefCell::new(AbsolutelyPositionedBox::construct(
861                    context,
862                    info,
863                    display_inside,
864                    contents,
865                )),
866            )),
867            BlockLevelCreator::OutOfFlowFloatBox {
868                display_inside,
869                contents,
870            } => ArcRefCell::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
871                context,
872                info,
873                display_inside,
874                contents,
875                self.propagated_data,
876            ))),
877            BlockLevelCreator::OutsideMarker {
878                contents,
879                list_item_style,
880            } => {
881                let contents = NonReplacedContents::OfPseudoElement(contents);
882                let block_container = BlockContainer::construct(
883                    context,
884                    info,
885                    contents,
886                    self.propagated_data,
887                    false, /* is_list_item */
888                );
889                // An outside ::marker must establish a BFC, and can't contain floats.
890                let block_formatting_context = BlockFormattingContext {
891                    contents: block_container,
892                    contains_floats: false,
893                };
894                ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker {
895                    context: IndependentFormattingContext::new(
896                        LayoutBoxBase::new(info.into(), info.style.clone()),
897                        IndependentFormattingContextContents::Flow(block_formatting_context),
898                        self.propagated_data,
899                    ),
900                    list_item_style,
901                }))
902            },
903            BlockLevelCreator::AnonymousTable { table_block } => table_block,
904        };
905        self.box_slot
906            .set(LayoutBox::BlockLevel(block_level_box.clone()));
907        block_level_box
908    }
909}
910
911impl IntermediateBlockContainer {
912    fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer {
913        match self {
914            IntermediateBlockContainer::Deferred {
915                contents,
916                propagated_data,
917                is_list_item,
918            } => BlockContainer::construct(context, info, contents, propagated_data, is_list_item),
919            IntermediateBlockContainer::InlineFormattingContext(block_container) => block_container,
920        }
921    }
922}
923
924#[cfg(test)]
925mod tests {
926    use super::*;
927
928    fn assert_first_letter_eq(text: &str, expected: &str) {
929        let range = first_letter_range(text);
930        assert_eq!(&text[range], expected);
931    }
932
933    #[test]
934    fn test_first_letter_range() {
935        // All spaces
936        assert_first_letter_eq("", "");
937        assert_first_letter_eq("  ", "");
938
939        // Spaces and punctuation only
940        assert_first_letter_eq("(", "");
941        assert_first_letter_eq(" (", "");
942        assert_first_letter_eq("( ", "");
943        assert_first_letter_eq("()", "");
944
945        // Invalid chars
946        assert_first_letter_eq("\u{0903}", "");
947
948        // First letter only
949        assert_first_letter_eq("A", "A");
950        assert_first_letter_eq(" A", "A");
951        assert_first_letter_eq("A ", "A");
952        assert_first_letter_eq(" A ", "A");
953
954        // Word
955        assert_first_letter_eq("App", "A");
956        assert_first_letter_eq(" App", "A");
957        assert_first_letter_eq("App ", "A");
958
959        // Preceding punctuation(s), intervening spaces and first letter
960        assert_first_letter_eq(r#""A"#, r#""A"#);
961        assert_first_letter_eq(r#" "A"#, r#""A"#);
962        assert_first_letter_eq(r#""A "#, r#""A"#);
963        assert_first_letter_eq(r#"" A"#, r#"" A"#);
964        assert_first_letter_eq(r#" "A "#, r#""A"#);
965        assert_first_letter_eq(r#"("A"#, r#"("A"#);
966        assert_first_letter_eq(r#" ("A"#, r#"("A"#);
967        assert_first_letter_eq(r#"( "A"#, r#"( "A"#);
968        assert_first_letter_eq(r#"[ ( "A"#, r#"[ ( "A"#);
969
970        // First letter and succeeding punctuation(s)
971        // TODO: modify test cases when intervening spaces in succeeding puntuations is supported
972        assert_first_letter_eq(r#"A""#, r#"A""#);
973        assert_first_letter_eq(r#"A" "#, r#"A""#);
974        assert_first_letter_eq(r#"A)]"#, r#"A)]"#);
975        assert_first_letter_eq(r#"A" )]"#, r#"A""#);
976        assert_first_letter_eq(r#"A)] >"#, r#"A)]"#);
977
978        // All
979        assert_first_letter_eq(r#" ("A" )]"#, r#"("A""#);
980        assert_first_letter_eq(r#" ("A")] >"#, r#"("A")]"#);
981
982        // Non ASCII chars
983        assert_first_letter_eq("一", "一");
984        assert_first_letter_eq(" 一 ", "一");
985        assert_first_letter_eq("一二三", "一");
986        assert_first_letter_eq(" 一二三 ", "一");
987        assert_first_letter_eq("(一二三)", "(一");
988        assert_first_letter_eq(" (一二三) ", "(一");
989        assert_first_letter_eq("((一", "((一");
990        assert_first_letter_eq(" ( (一", "( (一");
991        assert_first_letter_eq("一)", "一)");
992        assert_first_letter_eq("一))", "一))");
993        assert_first_letter_eq("一) )", "一)");
994    }
995}