layout/table/
construct.rs

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