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