1use 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;
14
15use super::OutsideMarker;
16use super::inline::construct::InlineFormattingContextBuilder;
17use super::inline::inline_box::InlineBox;
18use super::inline::{InlineFormattingContext, SharedInlineStyles};
19use crate::PropagatedBoxTreeData;
20use crate::cell::ArcRefCell;
21use crate::context::LayoutContext;
22use crate::dom::{BoxSlot, LayoutBox, NodeExt};
23use crate::dom_traversal::{
24 Contents, NodeAndStyleInfo, NonReplacedContents, PseudoElementContentItem, TraversalHandler,
25};
26use crate::flow::float::FloatBox;
27use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
28use crate::formatting_contexts::IndependentFormattingContext;
29use crate::fragment_tree::FragmentFlags;
30use crate::layout_box_base::LayoutBoxBase;
31use crate::positioned::AbsolutelyPositionedBox;
32use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, DisplayOutside};
33use crate::table::{AnonymousTableContent, Table};
34
35impl BlockFormattingContext {
36 pub(crate) fn construct(
37 context: &LayoutContext,
38 info: &NodeAndStyleInfo<'_>,
39 contents: NonReplacedContents,
40 propagated_data: PropagatedBoxTreeData,
41 is_list_item: bool,
42 ) -> Self {
43 Self::from_block_container(BlockContainer::construct(
44 context,
45 info,
46 contents,
47 propagated_data,
48 is_list_item,
49 ))
50 }
51
52 pub(crate) fn from_block_container(contents: BlockContainer) -> Self {
53 let contains_floats = contents.contains_floats();
54 Self {
55 contents,
56 contains_floats,
57 }
58 }
59}
60
61struct BlockLevelJob<'dom> {
62 info: NodeAndStyleInfo<'dom>,
63 box_slot: BoxSlot<'dom>,
64 propagated_data: PropagatedBoxTreeData,
65 kind: BlockLevelCreator,
66}
67
68enum BlockLevelCreator {
69 SameFormattingContextBlock(IntermediateBlockContainer),
70 Independent {
71 display_inside: DisplayInside,
72 contents: Contents,
73 },
74 OutOfFlowAbsolutelyPositionedBox {
75 display_inside: DisplayInside,
76 contents: Contents,
77 },
78 OutOfFlowFloatBox {
79 display_inside: DisplayInside,
80 contents: Contents,
81 },
82 OutsideMarker {
83 list_item_style: Arc<ComputedValues>,
84 contents: Vec<PseudoElementContentItem>,
85 },
86 AnonymousTable {
87 table_block: ArcRefCell<BlockLevelBox>,
88 },
89}
90
91enum IntermediateBlockContainer {
99 InlineFormattingContext(BlockContainer),
100 Deferred {
101 contents: NonReplacedContents,
102 propagated_data: PropagatedBoxTreeData,
103 is_list_item: bool,
104 },
105}
106
107pub(crate) struct BlockContainerBuilder<'dom, 'style> {
112 context: &'style LayoutContext<'style>,
113
114 info: &'style NodeAndStyleInfo<'dom>,
117
118 block_level_boxes: Vec<BlockLevelJob<'dom>>,
132
133 have_already_seen_first_line_for_text_indent: bool,
136
137 propagated_data: PropagatedBoxTreeData,
139
140 inline_formatting_context_builder: Option<InlineFormattingContextBuilder>,
145
146 anonymous_box_info: Option<NodeAndStyleInfo<'dom>>,
149
150 anonymous_table_content: Vec<AnonymousTableContent<'dom>>,
154
155 display_contents_shared_styles: Vec<SharedInlineStyles>,
159}
160
161impl BlockContainer {
162 pub fn construct(
163 context: &LayoutContext,
164 info: &NodeAndStyleInfo<'_>,
165 contents: NonReplacedContents,
166 propagated_data: PropagatedBoxTreeData,
167 is_list_item: bool,
168 ) -> BlockContainer {
169 let mut builder = BlockContainerBuilder::new(context, info, propagated_data);
170
171 if is_list_item {
172 if let Some((marker_info, marker_contents)) = crate::lists::make_marker(context, info) {
173 match marker_info.style.clone_list_style_position() {
174 ListStylePosition::Inside => {
175 builder.handle_list_item_marker_inside(&marker_info, marker_contents)
176 },
177 ListStylePosition::Outside => builder.handle_list_item_marker_outside(
178 &marker_info,
179 marker_contents,
180 info.style.clone(),
181 ),
182 }
183 }
184 }
185
186 contents.traverse(context, info, &mut builder);
187 builder.finish()
188 }
189}
190
191impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
192 pub(crate) fn new(
193 context: &'style LayoutContext,
194 info: &'style NodeAndStyleInfo<'dom>,
195 propagated_data: PropagatedBoxTreeData,
196 ) -> Self {
197 BlockContainerBuilder {
198 context,
199 info,
200 block_level_boxes: Vec::new(),
201 propagated_data,
202 have_already_seen_first_line_for_text_indent: false,
203 anonymous_box_info: None,
204 anonymous_table_content: Vec::new(),
205 inline_formatting_context_builder: None,
206 display_contents_shared_styles: Vec::new(),
207 }
208 }
209
210 fn currently_processing_inline_box(&self) -> bool {
211 self.inline_formatting_context_builder
212 .as_ref()
213 .is_some_and(InlineFormattingContextBuilder::currently_processing_inline_box)
214 }
215
216 fn ensure_inline_formatting_context_builder(&mut self) -> &mut InlineFormattingContextBuilder {
217 self.inline_formatting_context_builder
218 .get_or_insert_with(|| {
219 let mut builder = InlineFormattingContextBuilder::new(self.info);
220 for shared_inline_styles in self.display_contents_shared_styles.iter() {
221 builder.enter_display_contents(shared_inline_styles.clone());
222 }
223 builder
224 })
225 }
226
227 fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> {
228 self.inline_formatting_context_builder.take()?.finish(
229 self.context,
230 !self.have_already_seen_first_line_for_text_indent,
231 self.info.node.is_single_line_text_input(),
232 self.info.style.to_bidi_level(),
233 )
234 }
235
236 pub(crate) fn finish(mut self) -> BlockContainer {
237 debug_assert!(!self.currently_processing_inline_box());
238
239 self.finish_anonymous_table_if_needed();
240
241 if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context() {
242 if self.block_level_boxes.is_empty() {
246 return BlockContainer::InlineFormattingContext(inline_formatting_context);
247 }
248 self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
249 }
250
251 let context = self.context;
252 let block_level_boxes = if self.context.use_rayon {
253 self.block_level_boxes
254 .into_par_iter()
255 .map(|block_level_job| block_level_job.finish(context))
256 .collect()
257 } else {
258 self.block_level_boxes
259 .into_iter()
260 .map(|block_level_job| block_level_job.finish(context))
261 .collect()
262 };
263
264 BlockContainer::BlockLevelBoxes(block_level_boxes)
265 }
266
267 fn finish_anonymous_table_if_needed(&mut self) {
268 if self.anonymous_table_content.is_empty() {
269 return;
270 }
271
272 let inline_table = self.currently_processing_inline_box();
280
281 let contents: Vec<AnonymousTableContent<'dom>> =
282 self.anonymous_table_content.drain(..).collect();
283 let last_text = match contents.last() {
284 Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())),
285 _ => None,
286 };
287
288 let (table_info, ifc) =
289 Table::construct_anonymous(self.context, self.info, contents, self.propagated_data);
290
291 if inline_table {
292 self.ensure_inline_formatting_context_builder()
293 .push_atomic(|| ArcRefCell::new(ifc), None);
294 } else {
295 let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
296
297 if let Some(inline_formatting_context) = self.finish_ongoing_inline_formatting_context()
298 {
299 self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
300 }
301
302 let box_slot = table_info.node.box_slot();
303 self.block_level_boxes.push(BlockLevelJob {
304 info: table_info,
305 box_slot,
306 kind: BlockLevelCreator::AnonymousTable { table_block },
307 propagated_data: self.propagated_data,
308 });
309 }
310
311 if let Some((info, text)) = last_text {
321 self.handle_text(&info, text);
322 }
323 }
324}
325
326impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> {
327 fn handle_element(
328 &mut self,
329 info: &NodeAndStyleInfo<'dom>,
330 display: DisplayGeneratingBox,
331 contents: Contents,
332 box_slot: BoxSlot<'dom>,
333 ) {
334 match display {
335 DisplayGeneratingBox::OutsideInside { outside, inside } => {
336 self.finish_anonymous_table_if_needed();
337
338 match outside {
339 DisplayOutside::Inline => {
340 self.handle_inline_level_element(info, inside, contents, box_slot)
341 },
342 DisplayOutside::Block => {
343 let box_style = info.style.get_box();
344 if box_style.position.is_absolutely_positioned() {
347 self.handle_absolutely_positioned_element(
348 info, inside, contents, box_slot,
349 )
350 } else if box_style.float.is_floating() {
351 self.handle_float_element(info, inside, contents, box_slot)
352 } else {
353 self.handle_block_level_element(info, inside, contents, box_slot)
354 }
355 },
356 };
357 },
358 DisplayGeneratingBox::LayoutInternal(_) => {
359 self.anonymous_table_content
360 .push(AnonymousTableContent::Element {
361 info: info.clone(),
362 display,
363 contents,
364 box_slot,
365 });
366 },
367 }
368 }
369
370 fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
371 if text.is_empty() {
372 return;
373 }
374
375 if !self.anonymous_table_content.is_empty() && text.chars().all(char_is_whitespace) {
379 self.anonymous_table_content
380 .push(AnonymousTableContent::Text(info.clone(), text));
381 return;
382 } else {
383 self.finish_anonymous_table_if_needed();
384 }
385
386 self.ensure_inline_formatting_context_builder()
387 .push_text(text, info);
388 }
389
390 fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
391 self.display_contents_shared_styles.push(styles.clone());
392 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
393 builder.enter_display_contents(styles);
394 }
395 }
396
397 fn leave_display_contents(&mut self) {
398 self.display_contents_shared_styles.pop();
399 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
400 builder.leave_display_contents();
401 }
402 }
403}
404
405impl<'dom> BlockContainerBuilder<'dom, '_> {
406 fn handle_list_item_marker_inside(
407 &mut self,
408 marker_info: &NodeAndStyleInfo<'dom>,
409 contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
410 ) {
411 let box_slot = marker_info.node.box_slot();
412 self.handle_inline_level_element(
413 marker_info,
414 DisplayInside::Flow {
415 is_list_item: false,
416 },
417 NonReplacedContents::OfPseudoElement(contents).into(),
418 box_slot,
419 );
420 }
421
422 fn handle_list_item_marker_outside(
423 &mut self,
424 marker_info: &NodeAndStyleInfo<'dom>,
425 contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
426 list_item_style: Arc<ComputedValues>,
427 ) {
428 let box_slot = marker_info.node.box_slot();
429 self.block_level_boxes.push(BlockLevelJob {
430 info: marker_info.clone(),
431 box_slot,
432 kind: BlockLevelCreator::OutsideMarker {
433 contents,
434 list_item_style,
435 },
436 propagated_data: self.propagated_data,
437 });
438 }
439
440 fn handle_inline_level_element(
441 &mut self,
442 info: &NodeAndStyleInfo<'dom>,
443 display_inside: DisplayInside,
444 contents: Contents,
445 box_slot: BoxSlot<'dom>,
446 ) {
447 let old_layout_box = box_slot.take_layout_box_if_undamaged(info.damage);
448 let (is_list_item, non_replaced_contents) = match (display_inside, contents) {
449 (
450 DisplayInside::Flow { is_list_item },
451 Contents::NonReplaced(non_replaced_contents),
452 ) => (is_list_item, non_replaced_contents),
453 (_, contents) => {
454 let context = self.context;
456 let propagated_data = self.propagated_data;
457
458 let construction_callback = || {
459 ArcRefCell::new(IndependentFormattingContext::construct(
460 context,
461 info,
462 display_inside,
463 contents,
464 propagated_data,
465 ))
466 };
467
468 let atomic = self
469 .ensure_inline_formatting_context_builder()
470 .push_atomic(construction_callback, old_layout_box);
471 box_slot.set(LayoutBox::InlineLevel(vec![atomic]));
472 return;
473 },
474 };
475
476 self.ensure_inline_formatting_context_builder()
479 .start_inline_box(
480 || ArcRefCell::new(InlineBox::new(info)),
481 None,
482 old_layout_box,
483 );
484
485 if is_list_item {
486 if let Some((marker_info, marker_contents)) =
487 crate::lists::make_marker(self.context, info)
488 {
489 self.handle_list_item_marker_inside(&marker_info, marker_contents)
493 }
494 }
495
496 non_replaced_contents.traverse(self.context, info, self);
498
499 self.finish_anonymous_table_if_needed();
500
501 box_slot.set(LayoutBox::InlineLevel(
507 self.inline_formatting_context_builder
508 .as_mut()
509 .expect("Should be building an InlineFormattingContext")
510 .end_inline_box(),
511 ));
512 }
513
514 fn handle_block_level_element(
515 &mut self,
516 info: &NodeAndStyleInfo<'dom>,
517 display_inside: DisplayInside,
518 contents: Contents,
519 box_slot: BoxSlot<'dom>,
520 ) {
521 if let Some(inline_formatting_context) = self
528 .inline_formatting_context_builder
529 .as_mut()
530 .and_then(|builder| {
531 builder.split_around_block_and_finish(
532 self.context,
533 !self.have_already_seen_first_line_for_text_indent,
534 self.info.style.to_bidi_level(),
535 )
536 })
537 {
538 self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
539 }
540
541 let propagated_data = self.propagated_data;
542 let kind = match contents {
543 Contents::NonReplaced(contents) => match display_inside {
544 DisplayInside::Flow { is_list_item }
545 if !info.style.establishes_block_formatting_context(
548 FragmentFlags::empty()
549 ) =>
550 {
551 BlockLevelCreator::SameFormattingContextBlock(
552 IntermediateBlockContainer::Deferred {
553 contents,
554 propagated_data,
555 is_list_item,
556 },
557 )
558 },
559 _ => BlockLevelCreator::Independent {
560 display_inside,
561 contents: contents.into(),
562 },
563 },
564 Contents::Replaced(contents) => {
565 let contents = Contents::Replaced(contents);
566 BlockLevelCreator::Independent {
567 display_inside,
568 contents,
569 }
570 },
571 };
572 self.block_level_boxes.push(BlockLevelJob {
573 info: info.clone(),
574 box_slot,
575 kind,
576 propagated_data,
577 });
578
579 self.have_already_seen_first_line_for_text_indent = true;
582 }
583
584 fn handle_absolutely_positioned_element(
585 &mut self,
586 info: &NodeAndStyleInfo<'dom>,
587 display_inside: DisplayInside,
588 contents: Contents,
589 box_slot: BoxSlot<'dom>,
590 ) {
591 let needs_inline_builder =
597 info.style.get_box().original_display.outside() == StyloDisplayOutside::Inline;
598 if needs_inline_builder {
599 self.ensure_inline_formatting_context_builder();
600 }
601 let inline_builder = self
602 .inline_formatting_context_builder
603 .as_mut()
604 .filter(|builder| needs_inline_builder || !builder.is_empty);
605 if let Some(inline_builder) = inline_builder {
606 let constructor = || {
607 ArcRefCell::new(AbsolutelyPositionedBox::construct(
608 self.context,
609 info,
610 display_inside,
611 contents,
612 ))
613 };
614 let old_layout_box = box_slot.take_layout_box_if_undamaged(info.damage);
615 let inline_level_box =
616 inline_builder.push_absolutely_positioned_box(constructor, old_layout_box);
617 box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
618 return;
619 }
620
621 let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
622 contents,
623 display_inside,
624 };
625 self.block_level_boxes.push(BlockLevelJob {
626 info: info.clone(),
627 box_slot,
628 kind,
629 propagated_data: self.propagated_data,
630 });
631 }
632
633 fn handle_float_element(
634 &mut self,
635 info: &NodeAndStyleInfo<'dom>,
636 display_inside: DisplayInside,
637 contents: Contents,
638 box_slot: BoxSlot<'dom>,
639 ) {
640 if let Some(builder) = self.inline_formatting_context_builder.as_mut() {
641 if !builder.is_empty {
642 let constructor = || {
643 ArcRefCell::new(FloatBox::construct(
644 self.context,
645 info,
646 display_inside,
647 contents,
648 self.propagated_data,
649 ))
650 };
651 let old_layout_box = box_slot.take_layout_box_if_undamaged(info.damage);
652 let inline_level_box = builder.push_float_box(constructor, old_layout_box);
653 box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
654 return;
655 }
656 }
657
658 let kind = BlockLevelCreator::OutOfFlowFloatBox {
659 contents,
660 display_inside,
661 };
662 self.block_level_boxes.push(BlockLevelJob {
663 info: info.clone(),
664 box_slot,
665 kind,
666 propagated_data: self.propagated_data,
667 });
668 }
669
670 fn push_block_level_job_for_inline_formatting_context(
671 &mut self,
672 inline_formatting_context: InlineFormattingContext,
673 ) {
674 let layout_context = self.context;
675 let anonymous_info = self
676 .anonymous_box_info
677 .get_or_insert_with(|| {
678 self.info
679 .with_pseudo_element(layout_context, PseudoElement::ServoAnonymousBox)
680 .expect("Should never fail to create anonymous box")
681 })
682 .clone();
683
684 let box_slot = anonymous_info.node.box_slot();
685 self.block_level_boxes.push(BlockLevelJob {
686 info: anonymous_info,
687 box_slot,
688 kind: BlockLevelCreator::SameFormattingContextBlock(
689 IntermediateBlockContainer::InlineFormattingContext(
690 BlockContainer::InlineFormattingContext(inline_formatting_context),
691 ),
692 ),
693 propagated_data: self.propagated_data,
694 });
695
696 self.have_already_seen_first_line_for_text_indent = true;
697 }
698}
699
700impl BlockLevelJob<'_> {
701 fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
702 let info = &self.info;
703
704 if !info.damage.has_box_damage() {
707 if let Some(block_level_box) = match self.box_slot.slot.as_ref() {
708 Some(box_slot) => match &*box_slot.borrow() {
709 Some(LayoutBox::BlockLevel(block_level_box)) => Some(block_level_box.clone()),
710 _ => None,
711 },
712 None => None,
713 } {
714 return block_level_box;
715 }
716 }
717
718 let block_level_box = match self.kind {
719 BlockLevelCreator::SameFormattingContextBlock(intermediate_block_container) => {
720 let contents = intermediate_block_container.finish(context, info);
721 let contains_floats = contents.contains_floats();
722 ArcRefCell::new(BlockLevelBox::SameFormattingContextBlock {
723 base: LayoutBoxBase::new(info.into(), info.style.clone()),
724 contents,
725 contains_floats,
726 })
727 },
728 BlockLevelCreator::Independent {
729 display_inside,
730 contents,
731 } => {
732 let context = IndependentFormattingContext::construct(
733 context,
734 info,
735 display_inside,
736 contents,
737 self.propagated_data,
738 );
739 ArcRefCell::new(BlockLevelBox::Independent(context))
740 },
741 BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
742 display_inside,
743 contents,
744 } => ArcRefCell::new(BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
745 ArcRefCell::new(AbsolutelyPositionedBox::construct(
746 context,
747 info,
748 display_inside,
749 contents,
750 )),
751 )),
752 BlockLevelCreator::OutOfFlowFloatBox {
753 display_inside,
754 contents,
755 } => ArcRefCell::new(BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
756 context,
757 info,
758 display_inside,
759 contents,
760 self.propagated_data,
761 ))),
762 BlockLevelCreator::OutsideMarker {
763 contents,
764 list_item_style,
765 } => {
766 let contents = NonReplacedContents::OfPseudoElement(contents);
767 let block_container = BlockContainer::construct(
768 context,
769 info,
770 contents,
771 self.propagated_data,
772 false, );
774 let block_formatting_context = BlockFormattingContext {
776 contents: block_container,
777 contains_floats: false,
778 };
779 ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker {
780 base: LayoutBoxBase::new(info.into(), info.style.clone()),
781 block_formatting_context,
782 list_item_style,
783 }))
784 },
785 BlockLevelCreator::AnonymousTable { table_block } => table_block,
786 };
787 self.box_slot
788 .set(LayoutBox::BlockLevel(block_level_box.clone()));
789 block_level_box
790 }
791}
792
793impl IntermediateBlockContainer {
794 fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer {
795 match self {
796 IntermediateBlockContainer::Deferred {
797 contents,
798 propagated_data,
799 is_list_item,
800 } => BlockContainer::construct(context, info, contents, propagated_data, is_list_item),
801 IntermediateBlockContainer::InlineFormattingContext(block_container) => block_container,
802 }
803 }
804}