aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/table/construct.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/table/construct.rs')
-rw-r--r--components/layout_2020/table/construct.rs362
1 files changed, 341 insertions, 21 deletions
diff --git a/components/layout_2020/table/construct.rs b/components/layout_2020/table/construct.rs
index 708113fd21d..5bde80ee67e 100644
--- a/components/layout_2020/table/construct.rs
+++ b/components/layout_2020/table/construct.rs
@@ -4,6 +4,7 @@
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
+use std::iter::repeat;
use log::warn;
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
@@ -13,7 +14,10 @@ use style::selector_parser::PseudoElement;
use style::str::char_is_whitespace;
use style::values::specified::TextDecorationLine;
-use super::{Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset};
+use super::{
+ Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset, TableTrack,
+ TableTrackGroup, TableTrackGroupType,
+};
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, NodeExt};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
@@ -224,30 +228,216 @@ impl TableBuilder {
Self::new(ComputedValues::initial_values().to_arc())
}
+ pub fn last_row_index_in_row_group_at_row_n(&self, n: usize) -> usize {
+ // TODO: This is just a linear search, because the idea is that there are
+ // generally less than or equal to three row groups, but if we notice a lot
+ // of web content with more, we can consider a binary search here.
+ for row_group in self.table.row_groups.iter() {
+ if row_group.track_range.start > n {
+ return row_group.track_range.start - 1;
+ }
+ }
+ self.table.size.height - 1
+ }
+
pub fn finish(mut self) -> Table {
- // Make sure that every row has the same number of cells.
+ self.do_missing_cells_fixup();
+ self.remove_extra_columns_and_column_groups();
+ self.reorder_first_thead_and_tfoot();
+ self.do_final_rowspan_calculation();
+ self.table
+ }
+
+ /// Do <https://drafts.csswg.org/css-tables/#missing-cells-fixup> which ensures
+ /// that every row has the same number of cells.
+ fn do_missing_cells_fixup(&mut self) {
for row in self.table.slots.iter_mut() {
row.resize_with(self.table.size.width, || TableSlot::Empty);
}
+ }
+
+ /// It's possible to define more table columns via `<colgroup>` and `<col>` elements
+ /// than actually exist in the table. In that case, remove these bogus columns
+ /// to prevent using them later in layout.
+ fn remove_extra_columns_and_column_groups(&mut self) {
+ let number_of_actual_table_columns = self.table.size.width;
+ self.table.columns.truncate(number_of_actual_table_columns);
+
+ let mut remove_from = None;
+ for (group_index, column_group) in self.table.column_groups.iter_mut().enumerate() {
+ if column_group.track_range.start >= number_of_actual_table_columns {
+ remove_from = Some(group_index);
+ break;
+ }
+ column_group.track_range.end = column_group
+ .track_range
+ .end
+ .min(number_of_actual_table_columns);
+ }
+
+ if let Some(remove_from) = remove_from {
+ self.table.column_groups.truncate(remove_from);
+ }
+ }
+
+ /// Reorder the first `<thead>` and `<tbody>` to be the first and last row groups respectively.
+ /// This requires fixing up all row group indices.
+ /// See <https://drafts.csswg.org/css-tables/#table-header-group> and
+ /// <https://drafts.csswg.org/css-tables/#table-footer-group>.
+ fn reorder_first_thead_and_tfoot(&mut self) {
+ let mut thead_index = None;
+ let mut tfoot_index = None;
+ for (row_group_index, row_group) in self.table.row_groups.iter().enumerate() {
+ if thead_index.is_none() && row_group.group_type == TableTrackGroupType::HeaderGroup {
+ thead_index = Some(row_group_index);
+ }
+ if tfoot_index.is_none() && row_group.group_type == TableTrackGroupType::FooterGroup {
+ tfoot_index = Some(row_group_index);
+ }
+ if thead_index.is_some() && tfoot_index.is_some() {
+ break;
+ }
+ }
+
+ if let Some(thead_index) = thead_index {
+ self.move_row_group_to_front(thead_index)
+ }
+
+ if let Some(mut tfoot_index) = tfoot_index {
+ // We may have moved a `<thead>` which means the original index we
+ // we found for this this <tfoot>` also needs to be updated!
+ if thead_index.unwrap_or(0) > tfoot_index {
+ tfoot_index += 1;
+ }
+ self.move_row_group_to_end(tfoot_index)
+ }
+ }
+
+ fn regenerate_track_ranges(&mut self) {
+ // Now update all track group ranges.
+ let mut current_row_group_index = None;
+ for (row_index, row) in self.table.rows.iter().enumerate() {
+ if current_row_group_index == row.group_index {
+ continue;
+ }
+
+ // Finish any row group that is currently being processed.
+ if let Some(current_group_index) = current_row_group_index {
+ self.table.row_groups[current_group_index].track_range.end = row_index;
+ }
+
+ // Start processing this new row group and update its starting index.
+ current_row_group_index = row.group_index;
+ if let Some(current_group_index) = current_row_group_index {
+ self.table.row_groups[current_group_index].track_range.start = row_index;
+ }
+ }
+
+ // Finish the last row group.
+ if let Some(current_group_index) = current_row_group_index {
+ self.table.row_groups[current_group_index].track_range.end = self.table.rows.len();
+ }
+ }
+
+ fn move_row_group_to_front(&mut self, index_to_move: usize) {
+ if index_to_move == 0 {
+ return;
+ }
+
+ // Move the slots associated with this group.
+ let row_range = self.table.row_groups[index_to_move].track_range.clone();
+ let removed_slots: Vec<Vec<TableSlot>> = self
+ .table
+ .slots
+ .splice(row_range.clone(), std::iter::empty())
+ .collect();
+ self.table.slots.splice(0..0, removed_slots);
+
+ // Move the rows associated with this group.
+ let removed_rows: Vec<TableTrack> = self
+ .table
+ .rows
+ .splice(row_range, std::iter::empty())
+ .collect();
+ self.table.rows.splice(0..0, removed_rows);
+
+ // Move the group itself.
+ let removed_row_group = self.table.row_groups.remove(index_to_move);
+ self.table.row_groups.insert(0, removed_row_group);
+
+ for row in self.table.rows.iter_mut() {
+ match row.group_index.as_mut() {
+ Some(group_index) if *group_index < index_to_move => *group_index += 1,
+ Some(group_index) if *group_index == index_to_move => *group_index = 0,
+ _ => {},
+ }
+ }
+
+ // Do this now, rather than after possibly moving a `<tfoot>` row group to the end,
+ // because moving row groups depends on an accurate `track_range` in every group.
+ self.regenerate_track_ranges();
+ }
+
+ fn move_row_group_to_end(&mut self, index_to_move: usize) {
+ let last_row_group_index = self.table.row_groups.len() - 1;
+ if index_to_move == last_row_group_index {
+ return;
+ }
+
+ // Move the slots associated with this group.
+ let row_range = self.table.row_groups[index_to_move].track_range.clone();
+ let removed_slots: Vec<Vec<TableSlot>> = self
+ .table
+ .slots
+ .splice(row_range.clone(), std::iter::empty())
+ .collect();
+ self.table.slots.extend(removed_slots);
+
+ // Move the rows associated with this group.
+ let removed_rows: Vec<TableTrack> = self
+ .table
+ .rows
+ .splice(row_range, std::iter::empty())
+ .collect();
+ self.table.rows.extend(removed_rows);
+
+ // Move the group itself.
+ let removed_row_group = self.table.row_groups.remove(index_to_move);
+ self.table.row_groups.push(removed_row_group);
+
+ for row in self.table.rows.iter_mut() {
+ match row.group_index.as_mut() {
+ Some(group_index) if *group_index > index_to_move => *group_index -= 1,
+ Some(group_index) if *group_index == index_to_move => {
+ *group_index = last_row_group_index
+ },
+ _ => {},
+ }
+ }
- // Turn all rowspan=0 rows into the real value to avoid having to
- // make the calculation continually during layout. In addition, make
- // sure that there are no rowspans that extend past the end of the
- // table.
+ self.regenerate_track_ranges();
+ }
+
+ /// Turn all rowspan=0 rows into the real value to avoid having to make the calculation
+ /// continually during layout. In addition, make sure that there are no rowspans that extend
+ /// past the end of their row group.
+ fn do_final_rowspan_calculation(&mut self) {
for row_index in 0..self.table.size.height {
+ let last_row_index_in_group = self.last_row_index_in_row_group_at_row_n(row_index);
for cell in self.table.slots[row_index].iter_mut() {
if let TableSlot::Cell(ref mut cell) = cell {
- let rowspan_to_end_of_table = self.table.size.height - row_index;
+ if cell.rowspan == 1 {
+ continue;
+ }
+ let rowspan_to_end_of_group = last_row_index_in_group - row_index + 1;
if cell.rowspan == 0 {
- cell.rowspan = rowspan_to_end_of_table;
+ cell.rowspan = rowspan_to_end_of_group;
} else {
- cell.rowspan = cell.rowspan.min(rowspan_to_end_of_table);
+ cell.rowspan = cell.rowspan.min(rowspan_to_end_of_group);
}
}
}
}
-
- self.table
}
fn current_y(&self) -> usize {
@@ -408,6 +598,9 @@ pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> {
builder: TableBuilder,
current_anonymous_row_content: Vec<AnonymousTableContent<'dom, Node>>,
+
+ /// The index of the current row group, if there is one.
+ current_row_group_index: Option<usize>,
}
impl<'style, 'dom, Node> TableBuilderTraversal<'style, 'dom, Node>
@@ -425,6 +618,7 @@ where
propagated_text_decoration_line,
builder: TableBuilder::new(info.style.clone()),
current_anonymous_row_content: Vec::new(),
+ current_row_group_index: None,
}
}
@@ -499,17 +693,26 @@ where
DisplayLayoutInternal::TableFooterGroup |
DisplayLayoutInternal::TableHeaderGroup => {
self.finish_anonymous_row_if_needed();
-
- // TODO: Should we fixup `rowspan=0` to the actual resolved value and
- // any other rowspans that have been cut short?
self.builder.incoming_rowspans.clear();
+
+ let next_row_index = self.builder.table.rows.len();
+ self.builder.table.row_groups.push(TableTrackGroup {
+ base_fragment_info: info.into(),
+ style: info.style.clone(),
+ group_type: internal.into(),
+ track_range: next_row_index..next_row_index,
+ });
+
+ let new_row_group_index = self.builder.table.row_groups.len() - 1;
+ self.current_row_group_index = Some(new_row_group_index);
NonReplacedContents::try_from(contents).unwrap().traverse(
self.context,
info,
self,
);
- // TODO: Handle style for row groups here.
+ self.current_row_group_index = None;
+ self.builder.incoming_rowspans.clear();
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
@@ -527,17 +730,87 @@ where
);
row_builder.finish();
+ self.builder.table.rows.push(TableTrack {
+ base_fragment_info: info.into(),
+ style: info.style.clone(),
+ group_index: self.current_row_group_index,
+ is_anonymous: false,
+ });
+
+ let last_row = self.builder.table.rows.len();
+ let row_group = self
+ .current_row_group_index
+ .map(|index| &mut self.builder.table.row_groups[index]);
+ if let Some(row_group) = row_group {
+ row_group.track_range.end = last_row;
+ }
+
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
},
- DisplayLayoutInternal::TableCaption |
- DisplayLayoutInternal::TableColumn |
- DisplayLayoutInternal::TableColumnGroup => {
- // TODO: Handle these other types of table elements.
+ DisplayLayoutInternal::TableColumn => {
+ let node = info.node.to_threadsafe();
+ let span = (node.get_span().unwrap_or(1) as usize).min(1000);
+ for _ in 0..span + 1 {
+ self.builder.table.columns.push(TableTrack {
+ base_fragment_info: info.into(),
+ style: info.style.clone(),
+ group_index: None,
+ is_anonymous: false,
+ })
+ }
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
},
+ DisplayLayoutInternal::TableColumnGroup => {
+ let column_group_index = self.builder.table.column_groups.len();
+ let mut column_group_builder = TableColumnGroupBuilder {
+ column_group_index,
+ columns: Vec::new(),
+ };
+
+ NonReplacedContents::try_from(contents).unwrap().traverse(
+ self.context,
+ info,
+ &mut column_group_builder,
+ );
+
+ let first_column = self.builder.table.columns.len();
+ if column_group_builder.columns.is_empty() {
+ let node = info.node.to_threadsafe();
+ let span = (node.get_span().unwrap_or(1) as usize).min(1000);
+
+ self.builder.table.columns.extend(
+ repeat(TableTrack {
+ base_fragment_info: info.into(),
+ style: info.style.clone(),
+ group_index: Some(column_group_index),
+ is_anonymous: true,
+ })
+ .take(span),
+ );
+ } else {
+ self.builder
+ .table
+ .columns
+ .extend(column_group_builder.columns);
+ }
+
+ self.builder.table.column_groups.push(TableTrackGroup {
+ base_fragment_info: info.into(),
+ style: info.style.clone(),
+ group_type: internal.into(),
+ track_range: first_column..self.builder.table.columns.len(),
+ });
+
+ ::std::mem::forget(box_slot);
+ },
+ DisplayLayoutInternal::TableCaption => {
+ // TODO: Handle table captions.
+ // We are doing this until we have actually set a Box for this `BoxSlot`.
+ ::std::mem::forget(box_slot);
+ },
DisplayLayoutInternal::TableCell => {
self.current_anonymous_row_content
.push(AnonymousTableContent::Element {
@@ -682,8 +955,8 @@ where
// when dealing with arbitrary DOM elements (perhaps created via
// script).
let node = info.node.to_threadsafe();
- let rowspan = std::cmp::min(node.get_rowspan() as usize, 65534);
- let colspan = std::cmp::min(node.get_colspan() as usize, 1000);
+ let rowspan = (node.get_rowspan().unwrap_or(1) as usize).min(65534);
+ let colspan = (node.get_colspan().unwrap_or(1) as usize).min(1000);
let contents = match contents.try_into() {
Ok(non_replaced_contents) => {
@@ -735,3 +1008,50 @@ where
}
}
}
+
+struct TableColumnGroupBuilder {
+ column_group_index: usize,
+ columns: Vec<TableTrack>,
+}
+
+impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder
+where
+ Node: NodeExt<'dom>,
+{
+ fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _text: Cow<'dom, str>) {}
+ fn handle_element(
+ &mut self,
+ info: &NodeAndStyleInfo<Node>,
+ display: DisplayGeneratingBox,
+ _contents: Contents,
+ box_slot: BoxSlot<'dom>,
+ ) {
+ // We are doing this until we have actually set a Box for this `BoxSlot`.
+ ::std::mem::forget(box_slot);
+
+ if !matches!(
+ display,
+ DisplayGeneratingBox::LayoutInternal(DisplayLayoutInternal::TableColumn)
+ ) {
+ return;
+ }
+ self.columns.push(TableTrack {
+ base_fragment_info: info.into(),
+ style: info.style.clone(),
+ group_index: Some(self.column_group_index),
+ is_anonymous: false,
+ });
+ }
+}
+
+impl From<DisplayLayoutInternal> for TableTrackGroupType {
+ fn from(value: DisplayLayoutInternal) -> Self {
+ match value {
+ DisplayLayoutInternal::TableColumnGroup => TableTrackGroupType::ColumnGroup,
+ DisplayLayoutInternal::TableFooterGroup => TableTrackGroupType::FooterGroup,
+ DisplayLayoutInternal::TableHeaderGroup => TableTrackGroupType::HeaderGroup,
+ DisplayLayoutInternal::TableRowGroup => TableTrackGroupType::RowGroup,
+ _ => unreachable!(),
+ }
+ }
+}