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