Skip to main content

layout/table/
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;
6use std::iter::repeat_n;
7
8use atomic_refcell::AtomicRef;
9use layout_api::LayoutNode;
10use log::warn;
11use servo_arc::Arc;
12use style::properties::ComputedValues;
13use style::properties::style_structs::Font;
14use style::selector_parser::PseudoElement;
15use style::str::char_is_whitespace;
16
17use super::{
18    Table, TableCaption, TableLevelBox, TableSlot, TableSlotCell, TableSlotCoordinates,
19    TableSlotOffset, TableTrack, TableTrackGroup, TableTrackGroupType,
20};
21use crate::cell::ArcRefCell;
22use crate::context::LayoutContext;
23use crate::dom::{BoxSlot, LayoutBox, NodeExt};
24use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
25use crate::flow::inline::SharedInlineStyles;
26use crate::flow::{BlockContainerBuilder, BlockFormattingContext};
27use crate::formatting_contexts::{
28    IndependentFormattingContext, IndependentFormattingContextContents,
29};
30use crate::fragment_tree::BaseFragmentInfo;
31use crate::layout_box_base::LayoutBoxBase;
32use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal};
33use crate::{PropagatedBoxTreeData, SharedStyle};
34
35/// A reference to a slot and its coordinates in the table
36#[derive(Debug)]
37pub(super) struct ResolvedSlotAndLocation<'a> {
38    pub cell: AtomicRef<'a, TableSlotCell>,
39    pub coords: TableSlotCoordinates,
40}
41
42impl ResolvedSlotAndLocation<'_> {
43    fn covers_cell_at(&self, coords: TableSlotCoordinates) -> bool {
44        let covered_in_x =
45            coords.x >= self.coords.x && coords.x < self.coords.x + self.cell.colspan;
46        let covered_in_y = coords.y >= self.coords.y &&
47            (self.cell.rowspan == 0 || coords.y < self.coords.y + self.cell.rowspan);
48        covered_in_x && covered_in_y
49    }
50}
51
52pub(crate) enum AnonymousTableContent<'dom> {
53    Text(NodeAndStyleInfo<'dom>, Cow<'dom, str>),
54    EnterDisplayContents(SharedInlineStyles),
55    LeaveDisplayContents,
56    Element {
57        info: NodeAndStyleInfo<'dom>,
58        display: DisplayGeneratingBox,
59        contents: Contents,
60        box_slot: BoxSlot<'dom>,
61    },
62}
63
64impl AnonymousTableContent<'_> {
65    fn is_whitespace_only(&self) -> bool {
66        match self {
67            Self::Element { .. } => false,
68            Self::Text(_, text) => text.chars().all(char_is_whitespace),
69            Self::EnterDisplayContents(_) | Self::LeaveDisplayContents => true,
70        }
71    }
72
73    fn contents_are_whitespace_only(contents: &[Self]) -> bool {
74        contents.iter().all(|content| content.is_whitespace_only())
75    }
76}
77
78impl Table {
79    pub(crate) fn construct(
80        context: &LayoutContext,
81        info: &NodeAndStyleInfo,
82        grid_style: Arc<ComputedValues>,
83        contents: NonReplacedContents,
84        propagated_data: PropagatedBoxTreeData,
85    ) -> Self {
86        let mut traversal = TableBuilderTraversal::new(context, info, grid_style, propagated_data);
87        contents.traverse(context, info, &mut traversal);
88        traversal.finish()
89    }
90
91    pub(crate) fn construct_anonymous<'dom>(
92        context: &LayoutContext,
93        parent: &mut impl TraversalHandler<'dom>,
94        parent_info: &NodeAndStyleInfo<'dom>,
95        contents: Vec<AnonymousTableContent<'dom>>,
96        propagated_data: PropagatedBoxTreeData,
97    ) -> (NodeAndStyleInfo<'dom>, IndependentFormattingContext) {
98        let table_info = parent_info
99            .with_pseudo_element(context, PseudoElement::ServoAnonymousTable)
100            .expect("Should never fail to create anonymous table info.");
101        let table_style = table_info.style.clone();
102        let mut table_builder =
103            TableBuilderTraversal::new(context, &table_info, table_style.clone(), propagated_data);
104
105        for content in contents {
106            match content {
107                AnonymousTableContent::Element {
108                    info,
109                    display,
110                    contents,
111                    box_slot,
112                } => {
113                    table_builder.handle_element(&info, display, contents, box_slot);
114                },
115                AnonymousTableContent::Text(..) => {
116                    // This only happens if there was whitespace between our internal table elements.
117                    // We only collect that whitespace in case we need to re-emit trailing whitespace
118                    // after we've added our anonymous table.
119                },
120                // Since the table builder skips text, we don't have to handle `display: contents` there.
121                // But we need to handle it for the parent builder, because it may contain trailing
122                // whitespace which won't be placed inside the table.
123                AnonymousTableContent::EnterDisplayContents(styles) => {
124                    parent.enter_display_contents(styles)
125                },
126                AnonymousTableContent::LeaveDisplayContents => parent.leave_display_contents(),
127            }
128        }
129
130        let mut table = table_builder.finish();
131        table.anonymous = true;
132
133        let ifc = IndependentFormattingContext::new(
134            LayoutBoxBase::new((&table_info).into(), table_style),
135            IndependentFormattingContextContents::Table(table),
136            propagated_data,
137        );
138
139        (table_info, ifc)
140    }
141
142    /// Push a new slot into the last row of this table.
143    fn push_new_slot_to_last_row(&mut self, slot: TableSlot) {
144        let last_row = match self.slots.last_mut() {
145            Some(row) => row,
146            None => {
147                unreachable!("Should have some rows before calling `push_new_slot_to_last_row`")
148            },
149        };
150
151        self.size.width = self.size.width.max(last_row.len() + 1);
152        last_row.push(slot);
153    }
154
155    /// Find [`ResolvedSlotAndLocation`] of all the slots that cover the slot at the given
156    /// coordinates. This recursively resolves all of the [`TableSlotCell`]s that cover
157    /// the target and returns a [`ResolvedSlotAndLocation`] for each of them. If there is
158    /// no slot at the given coordinates or that slot is an empty space, an empty vector
159    /// is returned.
160    pub(super) fn resolve_slot_at(
161        &self,
162        coords: TableSlotCoordinates,
163    ) -> Vec<ResolvedSlotAndLocation<'_>> {
164        let slot = self.get_slot(coords);
165        match slot {
166            Some(TableSlot::Cell(cell)) => vec![ResolvedSlotAndLocation {
167                cell: cell.borrow(),
168                coords,
169            }],
170            Some(TableSlot::Spanned(offsets)) => offsets
171                .iter()
172                .flat_map(|offset| self.resolve_slot_at(coords - *offset))
173                .collect(),
174            Some(TableSlot::Empty) | None => {
175                warn!("Tried to resolve an empty or nonexistant slot!");
176                vec![]
177            },
178        }
179    }
180}
181
182impl TableSlot {
183    /// Merge a TableSlot::Spanned(x, y) with this (only for model errors)
184    pub fn push_spanned(&mut self, new_offset: TableSlotOffset) {
185        match *self {
186            TableSlot::Cell { .. } => {
187                panic!(
188                    "Should never have a table model error with an originating cell slot overlapping a spanned slot"
189                )
190            },
191            TableSlot::Spanned(ref mut vec) => vec.insert(0, new_offset),
192            TableSlot::Empty => {
193                panic!("Should never have a table model error with an empty slot");
194            },
195        }
196    }
197}
198
199pub struct TableBuilder {
200    /// The table that we are building.
201    table: Table,
202
203    /// An incoming rowspan is a value indicating that a cell in a row above the current row,
204    /// had a rowspan value other than 1. The values in this array indicate how many more
205    /// rows the cell should span. For example, a value of 0 at an index before `current_x()`
206    /// indicates that the cell on that column will not span into the next row, and at an index
207    /// after `current_x()` it indicates that the cell will not span into the current row.
208    /// A negative value means that the cell will span all remaining rows in the row group.
209    ///
210    /// As each column in a row is processed, the values in this vector are updated for the
211    /// next row.
212    pub incoming_rowspans: Vec<isize>,
213}
214
215impl TableBuilder {
216    pub(super) fn new(
217        style: Arc<ComputedValues>,
218        grid_style: Arc<ComputedValues>,
219        base_fragment_info: BaseFragmentInfo,
220        percentage_columns_allowed_for_inline_content_sizes: bool,
221    ) -> Self {
222        Self {
223            table: Table::new(
224                style,
225                grid_style,
226                base_fragment_info,
227                percentage_columns_allowed_for_inline_content_sizes,
228            ),
229            incoming_rowspans: Vec::new(),
230        }
231    }
232
233    pub fn new_for_tests() -> Self {
234        let testing_style =
235            ComputedValues::initial_values_with_font_override(Font::initial_values());
236        Self::new(
237            testing_style.clone(),
238            testing_style,
239            BaseFragmentInfo::anonymous(),
240            true, /* percentage_columns_allowed_for_inline_content_sizes */
241        )
242    }
243
244    pub fn last_row_index_in_row_group_at_row_n(&self, n: usize) -> usize {
245        // TODO: This is just a linear search, because the idea is that there are
246        // generally less than or equal to three row groups, but if we notice a lot
247        // of web content with more, we can consider a binary search here.
248        for row_group in self.table.row_groups.iter() {
249            let row_group = row_group.borrow();
250            if row_group.track_range.start > n {
251                return row_group.track_range.start - 1;
252            }
253        }
254        self.table.size.height - 1
255    }
256
257    pub fn finish(mut self) -> Table {
258        self.adjust_table_geometry_for_columns_and_colgroups();
259        self.do_missing_cells_fixup();
260        self.reorder_first_thead_and_tfoot();
261        self.do_final_rowspan_calculation();
262        self.table
263    }
264
265    /// Do <https://drafts.csswg.org/css-tables/#missing-cells-fixup> which ensures
266    /// that every row has the same number of cells.
267    fn do_missing_cells_fixup(&mut self) {
268        for row in self.table.slots.iter_mut() {
269            row.resize_with(self.table.size.width, || TableSlot::Empty);
270        }
271    }
272
273    /// It's possible to define more table columns via `<colgroup>` and `<col>` elements
274    /// than actually exist in the table. In that case, increase the size of the table.
275    ///
276    /// However, if the table has no row nor row group, remove the extra columns instead.
277    /// This matches WebKit, and some tests require it, but Gecko and Blink don't do it.
278    fn adjust_table_geometry_for_columns_and_colgroups(&mut self) {
279        if self.table.rows.is_empty() && self.table.row_groups.is_empty() {
280            self.table.columns.truncate(0);
281            self.table.column_groups.truncate(0);
282        } else {
283            self.table.size.width = self.table.size.width.max(self.table.columns.len());
284        }
285    }
286
287    /// Reorder the first `<thead>` and `<tbody>` to be the first and last row groups respectively.
288    /// This requires fixing up all row group indices.
289    /// See <https://drafts.csswg.org/css-tables/#table-header-group> and
290    /// <https://drafts.csswg.org/css-tables/#table-footer-group>.
291    fn reorder_first_thead_and_tfoot(&mut self) {
292        let mut thead_index = None;
293        let mut tfoot_index = None;
294        for (row_group_index, row_group) in self.table.row_groups.iter().enumerate() {
295            let row_group = row_group.borrow();
296            if thead_index.is_none() && row_group.group_type == TableTrackGroupType::HeaderGroup {
297                thead_index = Some(row_group_index);
298            }
299            if tfoot_index.is_none() && row_group.group_type == TableTrackGroupType::FooterGroup {
300                tfoot_index = Some(row_group_index);
301            }
302            if thead_index.is_some() && tfoot_index.is_some() {
303                break;
304            }
305        }
306
307        if let Some(thead_index) = thead_index {
308            self.move_row_group_to_front(thead_index)
309        }
310
311        if let Some(mut tfoot_index) = tfoot_index {
312            // We may have moved a `<thead>` which means the original index we
313            // we found for this this <tfoot>` also needs to be updated!
314            if thead_index.unwrap_or(0) > tfoot_index {
315                tfoot_index += 1;
316            }
317            self.move_row_group_to_end(tfoot_index)
318        }
319    }
320
321    fn regenerate_track_ranges(&mut self) {
322        // Now update all track group ranges.
323        let mut current_row_group_index = None;
324        for (row_index, row) in self.table.rows.iter().enumerate() {
325            let row = row.borrow();
326            if current_row_group_index == row.group_index {
327                continue;
328            }
329
330            // Finish any row group that is currently being processed.
331            if let Some(current_group_index) = current_row_group_index {
332                self.table.row_groups[current_group_index]
333                    .borrow_mut()
334                    .track_range
335                    .end = row_index;
336            }
337
338            // Start processing this new row group and update its starting index.
339            current_row_group_index = row.group_index;
340            if let Some(current_group_index) = current_row_group_index {
341                self.table.row_groups[current_group_index]
342                    .borrow_mut()
343                    .track_range
344                    .start = row_index;
345            }
346        }
347
348        // Finish the last row group.
349        if let Some(current_group_index) = current_row_group_index {
350            self.table.row_groups[current_group_index]
351                .borrow_mut()
352                .track_range
353                .end = self.table.rows.len();
354        }
355    }
356
357    fn move_row_group_to_front(&mut self, index_to_move: usize) {
358        // Move the group itself.
359        if index_to_move > 0 {
360            let removed_row_group = self.table.row_groups.remove(index_to_move);
361            self.table.row_groups.insert(0, removed_row_group);
362
363            for row in self.table.rows.iter_mut() {
364                let mut row = row.borrow_mut();
365                match row.group_index.as_mut() {
366                    Some(group_index) if *group_index < index_to_move => *group_index += 1,
367                    Some(group_index) if *group_index == index_to_move => *group_index = 0,
368                    _ => {},
369                }
370            }
371        }
372
373        let row_range = self.table.row_groups[0].borrow().track_range.clone();
374        if row_range.start > 0 {
375            // Move the slots associated with the moved group.
376            let removed_slots: Vec<Vec<TableSlot>> = self
377                .table
378                .slots
379                .splice(row_range.clone(), std::iter::empty())
380                .collect();
381            self.table.slots.splice(0..0, removed_slots);
382
383            // Move the rows associated with the moved group.
384            let removed_rows: Vec<_> = self
385                .table
386                .rows
387                .splice(row_range, std::iter::empty())
388                .collect();
389            self.table.rows.splice(0..0, removed_rows);
390
391            // Do this now, rather than after possibly moving a `<tfoot>` row group to the end,
392            // because moving row groups depends on an accurate `track_range` in every group.
393            self.regenerate_track_ranges();
394        }
395    }
396
397    fn move_row_group_to_end(&mut self, index_to_move: usize) {
398        let last_row_group_index = self.table.row_groups.len() - 1;
399
400        // Move the group itself.
401        if index_to_move < last_row_group_index {
402            let removed_row_group = self.table.row_groups.remove(index_to_move);
403            self.table.row_groups.push(removed_row_group);
404
405            for row in self.table.rows.iter_mut() {
406                let mut row = row.borrow_mut();
407                match row.group_index.as_mut() {
408                    Some(group_index) if *group_index > index_to_move => *group_index -= 1,
409                    Some(group_index) if *group_index == index_to_move => {
410                        *group_index = last_row_group_index
411                    },
412                    _ => {},
413                }
414            }
415        }
416
417        let row_range = self.table.row_groups[last_row_group_index]
418            .borrow()
419            .track_range
420            .clone();
421        if row_range.end < self.table.rows.len() {
422            // Move the slots associated with the moved group.
423            let removed_slots: Vec<Vec<TableSlot>> = self
424                .table
425                .slots
426                .splice(row_range.clone(), std::iter::empty())
427                .collect();
428            self.table.slots.extend(removed_slots);
429
430            // Move the rows associated with the moved group.
431            let removed_rows: Vec<_> = self
432                .table
433                .rows
434                .splice(row_range, std::iter::empty())
435                .collect();
436            self.table.rows.extend(removed_rows);
437
438            self.regenerate_track_ranges();
439        }
440    }
441
442    /// Turn all rowspan=0 rows into the real value to avoid having to make the calculation
443    /// continually during layout. In addition, make sure that there are no rowspans that extend
444    /// past the end of their row group.
445    fn do_final_rowspan_calculation(&mut self) {
446        for row_index in 0..self.table.size.height {
447            let last_row_index_in_group = self.last_row_index_in_row_group_at_row_n(row_index);
448            for cell in self.table.slots[row_index].iter_mut() {
449                if let TableSlot::Cell(cell) = cell {
450                    let mut cell = cell.borrow_mut();
451                    if cell.rowspan == 1 {
452                        continue;
453                    }
454                    let rowspan_to_end_of_group = last_row_index_in_group - row_index + 1;
455                    if cell.rowspan == 0 {
456                        cell.rowspan = rowspan_to_end_of_group;
457                    } else {
458                        cell.rowspan = cell.rowspan.min(rowspan_to_end_of_group);
459                    }
460                }
461            }
462        }
463    }
464
465    fn current_y(&self) -> Option<usize> {
466        self.table.slots.len().checked_sub(1)
467    }
468
469    fn current_x(&self) -> Option<usize> {
470        Some(self.table.slots[self.current_y()?].len())
471    }
472
473    fn current_coords(&self) -> Option<TableSlotCoordinates> {
474        Some(TableSlotCoordinates::new(
475            self.current_x()?,
476            self.current_y()?,
477        ))
478    }
479
480    pub fn start_row(&mut self) {
481        self.table.slots.push(Vec::new());
482        self.table.size.height += 1;
483        self.create_slots_for_cells_above_with_rowspan(true);
484    }
485
486    pub fn end_row(&mut self) {
487        // TODO: We need to insert a cell for any leftover non-table-like
488        // content in the TableRowBuilder.
489
490        // Truncate entries that are zero at the end of [`Self::incoming_rowspans`]. This
491        // prevents padding the table with empty cells when it isn't necessary.
492        let current_x = self
493            .current_x()
494            .expect("Should have rows before calling `end_row`");
495        for i in (current_x..self.incoming_rowspans.len()).rev() {
496            if self.incoming_rowspans[i] == 0 {
497                self.incoming_rowspans.pop();
498            } else {
499                break;
500            }
501        }
502
503        self.create_slots_for_cells_above_with_rowspan(false);
504    }
505
506    /// Create a [`TableSlot::Spanned`] for the target cell at the given coordinates. If
507    /// no slots cover the target, then this returns [`None`]. Note: This does not handle
508    /// slots that cover the target using `colspan`, but instead only considers slots that
509    /// cover this slot via `rowspan`. `colspan` should be handled by appending to the
510    /// return value of this function.
511    fn create_spanned_slot_based_on_cell_above(
512        &self,
513        target_coords: TableSlotCoordinates,
514    ) -> Option<TableSlot> {
515        let y_above = self.current_y()?.checked_sub(1)?;
516        let coords_for_slot_above = TableSlotCoordinates::new(target_coords.x, y_above);
517        let slots_covering_slot_above = self.table.resolve_slot_at(coords_for_slot_above);
518
519        let coords_of_slots_that_cover_target: Vec<_> = slots_covering_slot_above
520            .into_iter()
521            .filter(|slot| slot.covers_cell_at(target_coords))
522            .map(|slot| target_coords - slot.coords)
523            .collect();
524
525        if coords_of_slots_that_cover_target.is_empty() {
526            None
527        } else {
528            Some(TableSlot::Spanned(coords_of_slots_that_cover_target))
529        }
530    }
531
532    /// When not in the process of filling a cell, make sure any incoming rowspans are
533    /// filled so that the next specified cell comes after them. Should have been called before
534    /// [`Self::add_cell`]
535    ///
536    /// if `stop_at_cell_opportunity` is set, this will stop at the first slot with
537    /// `incoming_rowspans` equal to zero. If not, it will insert [`TableSlot::Empty`] and
538    /// continue to look for more incoming rowspans (which should only be done once we're
539    /// finished processing the cells in a row, and after calling truncating cells with
540    /// remaining rowspan from the end of `incoming_rowspans`.
541    fn create_slots_for_cells_above_with_rowspan(&mut self, stop_at_cell_opportunity: bool) {
542        let mut current_coords = self
543            .current_coords()
544            .expect("Should have rows before calling `create_slots_for_cells_above_with_rowspan`");
545        while let Some(span) = self.incoming_rowspans.get_mut(current_coords.x) {
546            // This column has no incoming rowspanned cells and `stop_at_zero` is true, so
547            // we should stop to process new cells defined in the current row.
548            if *span == 0 && stop_at_cell_opportunity {
549                break;
550            }
551
552            let new_cell = if *span != 0 {
553                *span -= 1;
554                self.create_spanned_slot_based_on_cell_above(current_coords)
555                    .expect(
556                        "Nonzero incoming rowspan cannot occur without a cell spanning this slot",
557                    )
558            } else {
559                TableSlot::Empty
560            };
561
562            self.table.push_new_slot_to_last_row(new_cell);
563            current_coords.x += 1;
564        }
565        debug_assert_eq!(Some(current_coords), self.current_coords());
566    }
567
568    /// <https://html.spec.whatwg.org/multipage/#algorithm-for-processing-rows>
569    /// Push a single cell onto the slot map, handling any colspans it may have, and
570    /// setting up the outgoing rowspans.
571    pub fn add_cell(&mut self, cell: ArcRefCell<TableSlotCell>) {
572        // Make sure the incoming_rowspans table is large enough
573        // because we will be writing to it.
574        let current_coords = self
575            .current_coords()
576            .expect("Should have rows before calling `add_cell`");
577
578        let (colspan, rowspan) = {
579            let cell = cell.borrow();
580            (cell.colspan, cell.rowspan)
581        };
582
583        if self.incoming_rowspans.len() < current_coords.x + colspan {
584            self.incoming_rowspans
585                .resize(current_coords.x + colspan, 0isize);
586        }
587
588        debug_assert_eq!(
589            self.incoming_rowspans[current_coords.x], 0,
590            "Added a cell in a position that also had an incoming rowspan!"
591        );
592
593        // If `rowspan` is zero, this is automatically negative and will stay negative.
594        let outgoing_rowspan = rowspan as isize - 1;
595        self.table.push_new_slot_to_last_row(TableSlot::Cell(cell));
596        self.incoming_rowspans[current_coords.x] = outgoing_rowspan;
597
598        // Draw colspanned cells
599        for colspan_offset in 1..colspan {
600            let current_x_plus_colspan_offset = current_coords.x + colspan_offset;
601            let new_offset = TableSlotOffset::new(colspan_offset, 0);
602            let incoming_rowspan = &mut self.incoming_rowspans[current_x_plus_colspan_offset];
603            let new_slot = if *incoming_rowspan == 0 {
604                *incoming_rowspan = outgoing_rowspan;
605                TableSlot::new_spanned(new_offset)
606            } else {
607                // This means we have a table model error.
608
609                // if `incoming_rowspan` is greater than zero, a cell from above is spanning
610                // into our row, colliding with the cells we are creating via colspan. In
611                // that case, set the incoming rowspan to the highest of two possible
612                // outgoing rowspan values (the incoming rowspan minus one, OR this cell's
613                // outgoing rowspan).  `spanned_slot()`` will handle filtering out
614                // inapplicable spans when it needs to.
615                //
616                // If the `incoming_rowspan` is negative we are in `rowspan=0` mode, (i.e.
617                // rowspan=infinity), so we don't have to worry about the current cell
618                // making it larger. In that case, don't change the rowspan.
619                if *incoming_rowspan > 0 {
620                    *incoming_rowspan = std::cmp::max(*incoming_rowspan - 1, outgoing_rowspan);
621                }
622
623                // This code creates a new slot in the case that there is a table model error.
624                let coords_of_spanned_cell =
625                    TableSlotCoordinates::new(current_x_plus_colspan_offset, current_coords.y);
626                let mut incoming_slot = self
627                    .create_spanned_slot_based_on_cell_above(coords_of_spanned_cell)
628                    .expect(
629                        "Nonzero incoming rowspan cannot occur without a cell spanning this slot",
630                    );
631                incoming_slot.push_spanned(new_offset);
632                incoming_slot
633            };
634            self.table.push_new_slot_to_last_row(new_slot);
635        }
636
637        debug_assert_eq!(
638            Some(TableSlotCoordinates::new(
639                current_coords.x + colspan,
640                current_coords.y
641            )),
642            self.current_coords(),
643            "Must have produced `colspan` slot entries!"
644        );
645        self.create_slots_for_cells_above_with_rowspan(true);
646    }
647}
648
649pub(crate) struct TableBuilderTraversal<'style, 'dom> {
650    context: &'style LayoutContext<'style>,
651    info: &'style NodeAndStyleInfo<'dom>,
652
653    /// The value of the [`PropagatedBoxTreeData`] to use, either for the row group
654    /// if processing one or for the table itself if outside a row group.
655    current_propagated_data: PropagatedBoxTreeData,
656
657    /// The [`TableBuilder`] for this [`TableBuilderTraversal`]. This is separated
658    /// into another struct so that we can write unit tests against the builder.
659    builder: TableBuilder,
660
661    current_anonymous_row_content: Vec<AnonymousTableContent<'dom>>,
662
663    /// The index of the current row group, if there is one.
664    current_row_group_index: Option<usize>,
665}
666
667impl<'style, 'dom> TableBuilderTraversal<'style, 'dom> {
668    pub(crate) fn new(
669        context: &'style LayoutContext<'style>,
670        info: &'style NodeAndStyleInfo<'dom>,
671        grid_style: Arc<ComputedValues>,
672        propagated_data: PropagatedBoxTreeData,
673    ) -> Self {
674        TableBuilderTraversal {
675            context,
676            info,
677            current_propagated_data: propagated_data,
678            builder: TableBuilder::new(
679                info.style.clone(),
680                grid_style,
681                info.into(),
682                propagated_data.allow_percentage_column_in_tables,
683            ),
684            current_anonymous_row_content: Vec::new(),
685            current_row_group_index: None,
686        }
687    }
688
689    pub(crate) fn finish(mut self) -> Table {
690        self.finish_anonymous_row_if_needed();
691        self.builder.finish()
692    }
693
694    fn finish_anonymous_row_if_needed(&mut self) {
695        if AnonymousTableContent::contents_are_whitespace_only(&self.current_anonymous_row_content)
696        {
697            self.current_anonymous_row_content.clear();
698            return;
699        }
700
701        let row_content = std::mem::take(&mut self.current_anonymous_row_content);
702        let anonymous_info = self
703            .info
704            .with_pseudo_element(self.context, PseudoElement::ServoAnonymousTableRow)
705            .expect("Should never fail to create anonymous row info.");
706        let mut row_builder =
707            TableRowBuilder::new(self, &anonymous_info, self.current_propagated_data);
708
709        for cell_content in row_content {
710            match cell_content {
711                AnonymousTableContent::Element {
712                    info,
713                    display,
714                    contents,
715                    box_slot,
716                } => {
717                    row_builder.handle_element(&info, display, contents, box_slot);
718                },
719                AnonymousTableContent::Text(info, text) => {
720                    row_builder.handle_text(&info, text);
721                },
722                AnonymousTableContent::EnterDisplayContents(styles) => {
723                    row_builder.enter_display_contents(styles)
724                },
725                AnonymousTableContent::LeaveDisplayContents => row_builder.leave_display_contents(),
726            }
727        }
728
729        row_builder.finish();
730
731        let style = anonymous_info.style.clone();
732        let table_row = ArcRefCell::new(TableTrack {
733            base: LayoutBoxBase::new((&anonymous_info).into(), style.clone()),
734            group_index: self.current_row_group_index,
735            is_anonymous: true,
736            shared_background_style: SharedStyle::new(style),
737        });
738        self.push_table_row(table_row.clone());
739
740        anonymous_info
741            .node
742            .box_slot()
743            .set(LayoutBox::TableLevelBox(TableLevelBox::Track(table_row)))
744    }
745
746    fn push_table_row(&mut self, table_track: ArcRefCell<TableTrack>) {
747        self.builder.table.rows.push(table_track);
748
749        let last_row = self.builder.table.rows.len();
750        if let Some(index) = self.current_row_group_index {
751            let row_group = &mut self.builder.table.row_groups[index];
752            row_group.borrow_mut().track_range.end = last_row;
753        }
754    }
755}
756
757impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> {
758    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
759        self.current_anonymous_row_content
760            .push(AnonymousTableContent::Text(info.clone(), text));
761    }
762
763    fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
764        self.current_anonymous_row_content
765            .push(AnonymousTableContent::EnterDisplayContents(styles));
766    }
767
768    fn leave_display_contents(&mut self) {
769        self.current_anonymous_row_content
770            .push(AnonymousTableContent::LeaveDisplayContents);
771    }
772
773    /// <https://html.spec.whatwg.org/multipage/#forming-a-table>
774    fn handle_element(
775        &mut self,
776        info: &NodeAndStyleInfo<'dom>,
777        display: DisplayGeneratingBox,
778        contents: Contents,
779        box_slot: BoxSlot<'dom>,
780    ) {
781        match display {
782            DisplayGeneratingBox::LayoutInternal(internal) => match internal {
783                DisplayLayoutInternal::TableRowGroup |
784                DisplayLayoutInternal::TableFooterGroup |
785                DisplayLayoutInternal::TableHeaderGroup => {
786                    self.finish_anonymous_row_if_needed();
787                    self.builder.incoming_rowspans.clear();
788
789                    let next_row_index = self.builder.table.rows.len();
790                    let row_group = ArcRefCell::new(TableTrackGroup {
791                        base: LayoutBoxBase::new(info.into(), info.style.clone()),
792                        group_type: internal.into(),
793                        track_range: next_row_index..next_row_index,
794                        shared_background_style: SharedStyle::new(info.style.clone()),
795                    });
796                    self.builder.table.row_groups.push(row_group.clone());
797
798                    let new_row_group_index = self.builder.table.row_groups.len() - 1;
799                    let context = self.context;
800                    let mut row_group_builder = TableRowGroupBuilder::new(
801                        self,
802                        info,
803                        self.current_propagated_data,
804                        new_row_group_index,
805                    );
806
807                    contents
808                        .non_replaced_contents()
809                        .expect("Replaced should not have a LayoutInternal display type.")
810                        .traverse(context, info, &mut row_group_builder);
811
812                    row_group_builder.finish();
813
814                    box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup(
815                        row_group,
816                    )));
817                },
818                DisplayLayoutInternal::TableRow => {
819                    self.finish_anonymous_row_if_needed();
820
821                    let context = self.context;
822                    let mut row_builder =
823                        TableRowBuilder::new(self, info, self.current_propagated_data);
824
825                    contents
826                        .non_replaced_contents()
827                        .expect("Replaced should not have a LayoutInternal display type.")
828                        .traverse(context, info, &mut row_builder);
829                    row_builder.finish();
830
831                    let row = ArcRefCell::new(TableTrack {
832                        base: LayoutBoxBase::new(info.into(), info.style.clone()),
833                        group_index: self.current_row_group_index,
834                        is_anonymous: false,
835                        shared_background_style: SharedStyle::new(info.style.clone()),
836                    });
837                    self.push_table_row(row.clone());
838                    box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row)));
839                },
840                DisplayLayoutInternal::TableColumn => {
841                    let old_box = box_slot.take_layout_box();
842                    let old_column = old_box.and_then(|layout_box| match layout_box {
843                        LayoutBox::TableLevelBox(TableLevelBox::Track(column)) => Some(column),
844                        _ => None,
845                    });
846                    let column = add_column(
847                        &mut self.builder.table.columns,
848                        info,
849                        None,  /* group_index */
850                        false, /* is_anonymous */
851                        old_column,
852                    );
853                    box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(column)));
854                },
855                DisplayLayoutInternal::TableColumnGroup => {
856                    let column_group_index = self.builder.table.column_groups.len();
857                    let mut column_group_builder = TableColumnGroupBuilder {
858                        column_group_index,
859                        columns: Vec::new(),
860                    };
861
862                    contents
863                        .non_replaced_contents()
864                        .expect("Replaced should not have a LayoutInternal display type.")
865                        .traverse(self.context, info, &mut column_group_builder);
866
867                    let first_column = self.builder.table.columns.len();
868                    if column_group_builder.columns.is_empty() {
869                        add_column(
870                            &mut self.builder.table.columns,
871                            info,
872                            Some(column_group_index),
873                            true, /* is_anonymous */
874                            None,
875                        );
876                    } else {
877                        self.builder
878                            .table
879                            .columns
880                            .extend(column_group_builder.columns);
881                    }
882
883                    let column_group = ArcRefCell::new(TableTrackGroup {
884                        base: LayoutBoxBase::new(info.into(), info.style.clone()),
885                        group_type: internal.into(),
886                        track_range: first_column..self.builder.table.columns.len(),
887                        shared_background_style: SharedStyle::new(info.style.clone()),
888                    });
889                    self.builder.table.column_groups.push(column_group.clone());
890                    box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup(
891                        column_group,
892                    )));
893                },
894                DisplayLayoutInternal::TableCaption => {
895                    let old_box = box_slot.take_layout_box();
896                    let old_caption = old_box.and_then(|layout_box| match layout_box {
897                        LayoutBox::TableLevelBox(TableLevelBox::Caption(caption)) => Some(caption),
898                        _ => None,
899                    });
900
901                    let caption = old_caption.unwrap_or_else(|| {
902                        let non_replaced_contents = contents
903                            .non_replaced_contents()
904                            .expect("Replaced should not have a LayoutInternal display type.");
905                        let contents = IndependentFormattingContextContents::Flow(
906                            BlockFormattingContext::construct(
907                                self.context,
908                                info,
909                                non_replaced_contents,
910                                self.current_propagated_data,
911                                false, /* is_list_item */
912                            ),
913                        );
914                        let base = LayoutBoxBase::new(info.into(), info.style.clone());
915                        ArcRefCell::new(TableCaption {
916                            context: IndependentFormattingContext::new(
917                                base,
918                                contents,
919                                self.current_propagated_data,
920                            ),
921                        })
922                    });
923
924                    self.builder.table.captions.push(caption.clone());
925                    box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Caption(caption)));
926                },
927                DisplayLayoutInternal::TableCell => {
928                    self.current_anonymous_row_content
929                        .push(AnonymousTableContent::Element {
930                            info: info.clone(),
931                            display,
932                            contents,
933                            box_slot,
934                        });
935                },
936            },
937            _ => {
938                self.current_anonymous_row_content
939                    .push(AnonymousTableContent::Element {
940                        info: info.clone(),
941                        display,
942                        contents,
943                        box_slot,
944                    });
945            },
946        }
947    }
948}
949
950struct TableRowGroupBuilder<'style, 'builder, 'dom, 'a> {
951    table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>,
952    info: &'a NodeAndStyleInfo<'dom>,
953    propagated_data: PropagatedBoxTreeData,
954    current_anonymous_row_content: Vec<AnonymousTableContent<'dom>>,
955}
956
957impl<'style, 'builder, 'dom, 'a> TableRowGroupBuilder<'style, 'builder, 'dom, 'a> {
958    fn new(
959        table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>,
960        info: &'a NodeAndStyleInfo<'dom>,
961        propagated_data: PropagatedBoxTreeData,
962        row_group_index: usize,
963    ) -> Self {
964        // Row groups are only opened from TableBuilderTraversal, never nested, so current_row_group_index is always None here.
965        debug_assert!(table_traversal.current_row_group_index.is_none());
966        table_traversal.current_row_group_index = Some(row_group_index);
967
968        Self {
969            table_traversal,
970            info,
971            propagated_data,
972            current_anonymous_row_content: Vec::new(),
973        }
974    }
975
976    fn finish(mut self) {
977        self.finish_anonymous_row_if_needed();
978        self.table_traversal.current_row_group_index = None;
979        self.table_traversal.builder.incoming_rowspans.clear();
980    }
981
982    fn finish_anonymous_row_if_needed(&mut self) {
983        if AnonymousTableContent::contents_are_whitespace_only(&self.current_anonymous_row_content)
984        {
985            self.current_anonymous_row_content.clear();
986            return;
987        }
988
989        let row_content = std::mem::take(&mut self.current_anonymous_row_content);
990        let anonymous_info = self
991            .info
992            .with_pseudo_element(
993                self.table_traversal.context,
994                PseudoElement::ServoAnonymousTableRow,
995            )
996            .expect("Should never fail to create anonymous row info.");
997
998        let mut row_builder =
999            TableRowBuilder::new(self.table_traversal, &anonymous_info, self.propagated_data);
1000
1001        for cell_content in row_content {
1002            match cell_content {
1003                AnonymousTableContent::Element {
1004                    info,
1005                    display,
1006                    contents,
1007                    box_slot,
1008                } => {
1009                    row_builder.handle_element(&info, display, contents, box_slot);
1010                },
1011                AnonymousTableContent::Text(info, text) => {
1012                    row_builder.handle_text(&info, text);
1013                },
1014                AnonymousTableContent::EnterDisplayContents(styles) => {
1015                    row_builder.enter_display_contents(styles)
1016                },
1017                AnonymousTableContent::LeaveDisplayContents => row_builder.leave_display_contents(),
1018            }
1019        }
1020
1021        row_builder.finish();
1022
1023        let style = anonymous_info.style.clone();
1024        let table_row = ArcRefCell::new(TableTrack {
1025            base: LayoutBoxBase::new((&anonymous_info).into(), style.clone()),
1026            group_index: self.table_traversal.current_row_group_index,
1027            is_anonymous: true,
1028            shared_background_style: SharedStyle::new(style),
1029        });
1030        self.table_traversal.push_table_row(table_row.clone());
1031
1032        anonymous_info
1033            .node
1034            .box_slot()
1035            .set(LayoutBox::TableLevelBox(TableLevelBox::Track(table_row)));
1036    }
1037}
1038
1039impl<'dom> TraversalHandler<'dom> for TableRowGroupBuilder<'_, '_, 'dom, '_> {
1040    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
1041        self.current_anonymous_row_content
1042            .push(AnonymousTableContent::Text(info.clone(), text));
1043    }
1044
1045    fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
1046        self.current_anonymous_row_content
1047            .push(AnonymousTableContent::EnterDisplayContents(styles));
1048    }
1049
1050    fn leave_display_contents(&mut self) {
1051        self.current_anonymous_row_content
1052            .push(AnonymousTableContent::LeaveDisplayContents);
1053    }
1054
1055    fn handle_element(
1056        &mut self,
1057        info: &NodeAndStyleInfo<'dom>,
1058        display: DisplayGeneratingBox,
1059        contents: Contents,
1060        box_slot: BoxSlot<'dom>,
1061    ) {
1062        match display {
1063            DisplayGeneratingBox::LayoutInternal(DisplayLayoutInternal::TableRow) => {
1064                self.finish_anonymous_row_if_needed();
1065
1066                let context = self.table_traversal.context;
1067                let mut row_builder =
1068                    TableRowBuilder::new(self.table_traversal, info, self.propagated_data);
1069
1070                contents
1071                    .non_replaced_contents()
1072                    .expect("Replaced should not have a LayoutInternal display type.")
1073                    .traverse(context, info, &mut row_builder);
1074                row_builder.finish();
1075
1076                let row = ArcRefCell::new(TableTrack {
1077                    base: LayoutBoxBase::new(info.into(), info.style.clone()),
1078                    group_index: self.table_traversal.current_row_group_index,
1079                    is_anonymous: false,
1080                    shared_background_style: SharedStyle::new(info.style.clone()),
1081                });
1082                self.table_traversal.push_table_row(row.clone());
1083                box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row)));
1084            },
1085
1086            _ => {
1087                self.current_anonymous_row_content
1088                    .push(AnonymousTableContent::Element {
1089                        info: info.clone(),
1090                        display,
1091                        contents,
1092                        box_slot,
1093                    });
1094            },
1095        }
1096    }
1097}
1098
1099struct TableRowBuilder<'style, 'builder, 'dom, 'a> {
1100    table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>,
1101
1102    /// The [`NodeAndStyleInfo`] of this table row, which we use to
1103    /// construct anonymous table cells.
1104    info: &'a NodeAndStyleInfo<'dom>,
1105
1106    current_anonymous_cell_content: Vec<AnonymousTableContent<'dom>>,
1107
1108    /// The [`PropagatedBoxTreeData`] to use for all children of this row.
1109    propagated_data: PropagatedBoxTreeData,
1110}
1111
1112impl<'style, 'builder, 'dom, 'a> TableRowBuilder<'style, 'builder, 'dom, 'a> {
1113    fn new(
1114        table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom>,
1115        info: &'a NodeAndStyleInfo<'dom>,
1116        propagated_data: PropagatedBoxTreeData,
1117    ) -> Self {
1118        table_traversal.builder.start_row();
1119
1120        TableRowBuilder {
1121            table_traversal,
1122            info,
1123            current_anonymous_cell_content: Vec::new(),
1124            propagated_data,
1125        }
1126    }
1127
1128    fn finish(mut self) {
1129        self.finish_current_anonymous_cell_if_needed();
1130        self.table_traversal.builder.end_row();
1131    }
1132
1133    fn finish_current_anonymous_cell_if_needed(&mut self) {
1134        if AnonymousTableContent::contents_are_whitespace_only(&self.current_anonymous_cell_content)
1135        {
1136            self.current_anonymous_cell_content.clear();
1137            return;
1138        }
1139
1140        let context = self.table_traversal.context;
1141        let anonymous_info = self
1142            .info
1143            .with_pseudo_element(context, PseudoElement::ServoAnonymousTableCell)
1144            .expect("Should never fail to create anonymous table cell info");
1145        let propagated_data = self.propagated_data.disallowing_percentage_table_columns();
1146        let mut builder = BlockContainerBuilder::new(context, &anonymous_info, propagated_data);
1147
1148        for cell_content in self.current_anonymous_cell_content.drain(..) {
1149            match cell_content {
1150                AnonymousTableContent::Element {
1151                    info,
1152                    display,
1153                    contents,
1154                    box_slot,
1155                } => {
1156                    builder.handle_element(&info, display, contents, box_slot);
1157                },
1158                AnonymousTableContent::Text(info, text) => {
1159                    builder.handle_text(&info, text);
1160                },
1161                AnonymousTableContent::EnterDisplayContents(styles) => {
1162                    builder.enter_display_contents(styles)
1163                },
1164                AnonymousTableContent::LeaveDisplayContents => builder.leave_display_contents(),
1165            }
1166        }
1167
1168        let block_container = builder.finish();
1169        let new_table_cell = ArcRefCell::new(TableSlotCell {
1170            context: IndependentFormattingContext::new(
1171                LayoutBoxBase::new(BaseFragmentInfo::anonymous(), anonymous_info.style),
1172                IndependentFormattingContextContents::Flow(
1173                    BlockFormattingContext::from_block_container(block_container),
1174                ),
1175                propagated_data,
1176            ),
1177            colspan: 1,
1178            rowspan: 1,
1179        });
1180        self.table_traversal
1181            .builder
1182            .add_cell(new_table_cell.clone());
1183
1184        anonymous_info
1185            .node
1186            .box_slot()
1187            .set(LayoutBox::TableLevelBox(TableLevelBox::Cell(
1188                new_table_cell,
1189            )));
1190    }
1191}
1192
1193impl<'dom> TraversalHandler<'dom> for TableRowBuilder<'_, '_, 'dom, '_> {
1194    fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
1195        self.current_anonymous_cell_content
1196            .push(AnonymousTableContent::Text(info.clone(), text));
1197    }
1198
1199    fn enter_display_contents(&mut self, styles: SharedInlineStyles) {
1200        self.current_anonymous_cell_content
1201            .push(AnonymousTableContent::EnterDisplayContents(styles));
1202    }
1203
1204    fn leave_display_contents(&mut self) {
1205        self.current_anonymous_cell_content
1206            .push(AnonymousTableContent::LeaveDisplayContents);
1207    }
1208
1209    /// <https://html.spec.whatwg.org/multipage/#algorithm-for-processing-rows>
1210    fn handle_element(
1211        &mut self,
1212        info: &NodeAndStyleInfo<'dom>,
1213        display: DisplayGeneratingBox,
1214        contents: Contents,
1215        box_slot: BoxSlot<'dom>,
1216    ) {
1217        #[allow(clippy::collapsible_match)] //// TODO: Remove once the other cases are handled
1218        match display {
1219            DisplayGeneratingBox::LayoutInternal(internal) => match internal {
1220                DisplayLayoutInternal::TableCell => {
1221                    self.finish_current_anonymous_cell_if_needed();
1222
1223                    let old_box = box_slot.take_layout_box();
1224                    let old_cell = old_box.and_then(|layout_box| match layout_box {
1225                        LayoutBox::TableLevelBox(TableLevelBox::Cell(cell)) => Some(cell),
1226                        _ => None,
1227                    });
1228
1229                    let cell = old_cell.unwrap_or_else(|| {
1230                        // This value will already have filtered out rowspan=0
1231                        // in quirks mode, so we don't have to worry about that.
1232                        let (rowspan, colspan) = if info.pseudo_element_chain().is_empty() {
1233                            let rowspan = info.node.table_rowspan().unwrap_or(1) as usize;
1234                            let colspan = info.node.table_colspan().unwrap_or(1) as usize;
1235
1236                            // The HTML specification clamps value of `rowspan` to [0, 65534] and
1237                            // `colspan` to [1, 1000].
1238                            assert!((1..=1000).contains(&colspan));
1239                            assert!((0..=65534).contains(&rowspan));
1240
1241                            (rowspan, colspan)
1242                        } else {
1243                            (1, 1)
1244                        };
1245
1246                        let propagated_data =
1247                            self.propagated_data.disallowing_percentage_table_columns();
1248                        let non_replaced_contents = contents
1249                            .non_replaced_contents()
1250                            .expect("Replaced should not have a LayoutInternal display type.");
1251
1252                        let contents = BlockFormattingContext::construct(
1253                            self.table_traversal.context,
1254                            info,
1255                            non_replaced_contents,
1256                            propagated_data,
1257                            false, /* is_list_item */
1258                        );
1259
1260                        ArcRefCell::new(TableSlotCell {
1261                            context: IndependentFormattingContext::new(
1262                                LayoutBoxBase::new(info.into(), info.style.clone()),
1263                                IndependentFormattingContextContents::Flow(contents),
1264                                propagated_data,
1265                            ),
1266                            colspan,
1267                            rowspan,
1268                        })
1269                    });
1270
1271                    self.table_traversal.builder.add_cell(cell.clone());
1272                    box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Cell(cell)));
1273                },
1274                _ => {
1275                    //// TODO: Properly handle other table-like elements in the middle of a row.
1276                    self.current_anonymous_cell_content
1277                        .push(AnonymousTableContent::Element {
1278                            info: info.clone(),
1279                            display,
1280                            contents,
1281                            box_slot,
1282                        });
1283                },
1284            },
1285            _ => {
1286                self.current_anonymous_cell_content
1287                    .push(AnonymousTableContent::Element {
1288                        info: info.clone(),
1289                        display,
1290                        contents,
1291                        box_slot,
1292                    });
1293            },
1294        }
1295    }
1296}
1297
1298struct TableColumnGroupBuilder {
1299    column_group_index: usize,
1300    columns: Vec<ArcRefCell<TableTrack>>,
1301}
1302
1303impl<'dom> TraversalHandler<'dom> for TableColumnGroupBuilder {
1304    fn handle_text(&mut self, _info: &NodeAndStyleInfo<'dom>, _text: Cow<'dom, str>) {}
1305    fn enter_display_contents(&mut self, _: SharedInlineStyles) {}
1306    fn leave_display_contents(&mut self) {}
1307    fn handle_element(
1308        &mut self,
1309        info: &NodeAndStyleInfo<'dom>,
1310        display: DisplayGeneratingBox,
1311        _contents: Contents,
1312        box_slot: BoxSlot<'dom>,
1313    ) {
1314        if !matches!(
1315            display,
1316            DisplayGeneratingBox::LayoutInternal(DisplayLayoutInternal::TableColumn)
1317        ) {
1318            // The BoxSlot destructor will check to ensure that it isn't empty but in this case, the
1319            // DOM node doesn't produce any box, so explicitly skip the destructor here.
1320            ::std::mem::forget(box_slot);
1321            return;
1322        }
1323        let old_box = box_slot.take_layout_box();
1324        let old_column = old_box.and_then(|layout_box| match layout_box {
1325            LayoutBox::TableLevelBox(TableLevelBox::Track(column)) => Some(column),
1326            _ => None,
1327        });
1328        let column = add_column(
1329            &mut self.columns,
1330            info,
1331            Some(self.column_group_index),
1332            false, /* is_anonymous */
1333            old_column,
1334        );
1335        box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(column)));
1336    }
1337}
1338
1339impl From<DisplayLayoutInternal> for TableTrackGroupType {
1340    fn from(value: DisplayLayoutInternal) -> Self {
1341        match value {
1342            DisplayLayoutInternal::TableColumnGroup => TableTrackGroupType::ColumnGroup,
1343            DisplayLayoutInternal::TableFooterGroup => TableTrackGroupType::FooterGroup,
1344            DisplayLayoutInternal::TableHeaderGroup => TableTrackGroupType::HeaderGroup,
1345            DisplayLayoutInternal::TableRowGroup => TableTrackGroupType::RowGroup,
1346            _ => unreachable!(),
1347        }
1348    }
1349}
1350
1351fn add_column(
1352    collection: &mut Vec<ArcRefCell<TableTrack>>,
1353    column_info: &NodeAndStyleInfo,
1354    group_index: Option<usize>,
1355    is_anonymous: bool,
1356    old_column: Option<ArcRefCell<TableTrack>>,
1357) -> ArcRefCell<TableTrack> {
1358    let span = if column_info.pseudo_element_chain().is_empty() {
1359        column_info.node.table_span().unwrap_or(1)
1360    } else {
1361        1
1362    };
1363
1364    // The HTML specification clamps value of `span` for `<col>` to [1, 1000].
1365    assert!((1..=1000).contains(&span));
1366
1367    let column = match old_column {
1368        Some(column) => {
1369            column.borrow_mut().group_index = group_index;
1370            column
1371        },
1372        None => ArcRefCell::new(TableTrack {
1373            base: LayoutBoxBase::new(column_info.into(), column_info.style.clone()),
1374            group_index,
1375            is_anonymous,
1376            shared_background_style: SharedStyle::new(column_info.style.clone()),
1377        }),
1378    };
1379    collection.extend(repeat_n(column.clone(), span as usize));
1380    column
1381}