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