diff --git a/components/layout/table.rs b/components/layout/table.rs index 72916de36ed..2f5de2aa1e4 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -496,10 +496,10 @@ impl Flow for TableFlow { }); } - fn assign_block_size(&mut self, _: &LayoutContext) { + fn assign_block_size(&mut self, lc: &LayoutContext) { debug!("assign_block_size: assigning block_size for table"); let vertical_spacing = self.spacing().vertical(); - self.block_flow.assign_block_size_for_table_like_flow(vertical_spacing) + self.block_flow.assign_block_size_for_table_like_flow(vertical_spacing, lc) } fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { @@ -567,13 +567,6 @@ impl Flow for TableFlow { } #[derive(Debug)] -// XXXManishearth We might be able to avoid the Arcs if -// the table is structured such that the columns always come -// first in the flow tree, at which point we can -// reuse the iterator that we use for colgroups -// for rows (and have no borrowing issues between -// holding on to both ColumnStyle<'table> and -// the rows) struct ColumnStyle<'table> { span: u32, colgroup_style: Option<&'table ComputedValues>, @@ -771,34 +764,95 @@ fn perform_border_collapse_for_row(child_table_row: &mut TableRowFlow, /// rowgroups. pub trait TableLikeFlow { /// Lays out the rows of a table. - fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au); + fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au, + layout_context: &LayoutContext); } impl TableLikeFlow for BlockFlow { - fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au) { + fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au, + layout_context: &LayoutContext) { debug_assert!(self.fragment.style.get_inheritedtable().border_collapse == border_collapse::T::Separate || block_direction_spacing == Au(0)); + fn border_spacing_for_row(fragment: &Fragment, row: &TableRowFlow, + block_direction_spacing: Au) -> Au { + match fragment.style.get_inheritedtable().border_collapse { + border_collapse::T::Separate => block_direction_spacing, + border_collapse::T::Collapse => { + row.collapsed_border_spacing.block_start + } + } + } + if self.base.restyle_damage.contains(ServoRestyleDamage::REFLOW) { + // (size, cumulative_border_spacing) + let mut sizes = vec![(Au(0), Au(0))]; + // The amount of border spacing up to and including this row, + // but not including the spacing beneath it + let mut cumulative_border = Au(0); + let mut incoming_rowspan_data = vec![]; + + // First pass: Compute block-direction border spacings + // XXXManishearth this can be done in tandem with the second pass, + // provided we never hit any rowspan cases + for kid in self.base.child_iter_mut() + .filter(|k| k.is_table_row()) + .skip(1) { + cumulative_border += + border_spacing_for_row(&self.fragment, kid.as_table_row(), + block_direction_spacing); + // we haven't calculated sizes yet + sizes.push((Au(0), cumulative_border)); + } + + // Second pass: Compute row block sizes + // [expensive: iterates over cells] + let mut i = 0; + let mut overflow = Au(0); + for kid in self.base.child_iter_mut() { + if kid.is_table_row() { + let (size, oflo) = kid.as_mut_table_row() + .compute_block_size_table_row_base(layout_context, + &mut incoming_rowspan_data, + &sizes, + i); + sizes[i].0 = size; + overflow = oflo; + i += 1; + // new rowgroups stop rowspans + } else if kid.is_table_rowgroup() { + if i > 0 { + sizes[i - 1].0 = cmp::max(sizes[i - 1].0, overflow); + } + } + } + if i > 0 { + sizes[i - 1].0 = cmp::max(sizes[i - 1].0, overflow); + } + + // Our current border-box position. let block_start_border_padding = self.fragment.border_padding.block_start; let mut current_block_offset = block_start_border_padding; let mut has_rows = false; + // Third pass: Assign block sizes and positions to rows, cells, and other children + // [expensive: iterates over cells] // At this point, `current_block_offset` is at the content edge of our box. Now iterate // over children. + let mut i = 0; for kid in self.base.child_iter_mut() { - // Account for spacing or collapsed borders. if kid.is_table_row() { has_rows = true; - let child_table_row = kid.as_table_row(); + let row = kid.as_mut_table_row(); + row.assign_block_size_to_self_and_children(&sizes, i); + row.mut_base().restyle_damage + .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | + ServoRestyleDamage::REFLOW); current_block_offset = current_block_offset + - match self.fragment.style.get_inheritedtable().border_collapse { - border_collapse::T::Separate => block_direction_spacing, - border_collapse::T::Collapse => { - child_table_row.collapsed_border_spacing.block_start - } - } + border_spacing_for_row(&self.fragment, row, + block_direction_spacing); + i += 1; } // At this point, `current_block_offset` is at the border edge of the child. @@ -843,6 +897,7 @@ impl TableLikeFlow for BlockFlow { self.fragment.border_box.start.b = Au(0); self.base.position.size.block = current_block_offset; + // Fourth pass: Assign absolute position info // Write in the size of the relative containing block for children. (This information // is also needed to handle RTL.) for kid in self.base.child_iter_mut() { diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index f3bbe14f2e7..fe8d453cb0b 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -148,6 +148,17 @@ impl TableCellFlow { } } } + + // Total block size of child + // + // Call after block size calculation + pub fn total_block_size(&mut self) -> Au { + // TODO: Percentage block-size + let specified = MaybeAuto::from_style(self.fragment().style() + .content_block_size(), + Au(0)).specified_or_zero(); + specified + self.fragment().border_padding.block_start_end() + } } impl Flow for TableCellFlow { diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 75fff53a969..dbe0bf7b665 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -19,7 +19,7 @@ use gfx_traits::print_tree::PrintTree; use layout_debug; use model::MaybeAuto; use serde::{Serialize, Serializer}; -use std::cmp::max; +use std::cmp::{max, min}; use std::fmt; use std::iter::{Enumerate, IntoIterator, Peekable}; use style::computed_values::border_collapse::T as BorderCollapse; @@ -27,7 +27,6 @@ use style::computed_values::border_spacing::T as BorderSpacing; use style::computed_values::border_top_style::T as BorderStyle; use style::logical_geometry::{LogicalSize, PhysicalSide, WritingMode}; use style::properties::ComputedValues; -use style::servo::restyle_damage::ServoRestyleDamage; use style::values::computed::{Color, LengthOrPercentageOrAuto}; use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt}; use table_cell::{CollapsedBordersForCell, TableCellFlow}; @@ -107,96 +106,154 @@ impl TableRowFlow { } } - /// Assign block-size for table-row flow. + /// Compute block-size for table-row flow. /// /// TODO(pcwalton): This doesn't handle floats and positioned elements right. /// - /// inline(always) because this is only ever called by in-order or non-in-order top-level - /// methods - #[inline(always)] - fn assign_block_size_table_row_base(&mut self, layout_context: &LayoutContext) { - if self.block_flow.base.restyle_damage.contains(ServoRestyleDamage::REFLOW) { - // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of - // all cells). - let mut max_block_size = Au(0); - let thread_id = self.block_flow.base.thread_id; - let content_box = self.block_flow.base.position - - self.block_flow.fragment.border_padding - - self.block_flow.fragment.margin; - for kid in self.block_flow.base.child_iter_mut() { - kid.place_float_if_applicable(); - if !kid.base().flags.is_float() { - kid.assign_block_size_for_inorder_child_if_necessary(layout_context, - thread_id, - content_box); + /// Returns the block size, as well as the size this should be if this is the last row + pub fn compute_block_size_table_row_base<'a>(&'a mut self, layout_context: &LayoutContext, + incoming_rowspan_data: &mut Vec, + border_info: &[(Au, Au)], // (_, cumulative_border_size) + row_index: usize) -> (Au, Au) { + fn include_sizes_from_previous_rows(col: &mut usize, + incoming_rowspan: &[u32], + incoming_rowspan_data: &mut Vec, + max_block_size: &mut Au, + largest_leftover_incoming_size: &mut Au) { + while let Some(span) = incoming_rowspan.get(*col) { + if *span <= 1 { + break; } - - { - let child_fragment = kid.as_mut_table_cell().fragment(); - // TODO: Percentage block-size - let child_specified_block_size = - MaybeAuto::from_style(child_fragment.style().content_block_size(), - Au(0)).specified_or_zero(); - max_block_size = - max(max_block_size, - child_specified_block_size + - child_fragment.border_padding.block_start_end()); + let incoming = incoming_rowspan_data[*col]; + *max_block_size = max(*max_block_size, incoming); + if *span > 2 { + *largest_leftover_incoming_size = max(*largest_leftover_incoming_size, + incoming * (*span - 1) as i32) } - let child_node = kid.mut_base(); - child_node.position.start.b = Au(0); - max_block_size = max(max_block_size, child_node.position.size.block); - } - - let mut block_size = max_block_size; - // TODO: Percentage block-size - block_size = match MaybeAuto::from_style(self.block_flow - .fragment - .style() - .content_block_size(), - Au(0)) { - MaybeAuto::Auto => block_size, - MaybeAuto::Specified(value) => max(value, block_size), - }; - - // Assign the block-size of own fragment - let mut position = self.block_flow.fragment.border_box; - position.size.block = block_size; - self.block_flow.fragment.border_box = position; - self.block_flow.base.position.size.block = block_size; - - // Assign the block-size of kid fragments, which is the same value as own block-size. - for kid in self.block_flow.base.child_iter_mut() { - let child_table_cell = kid.as_mut_table_cell(); - { - let kid_fragment = child_table_cell.mut_fragment(); - let mut position = kid_fragment.border_box; - position.size.block = block_size; - kid_fragment.border_box = position; - } - - // Assign the child's block size. - child_table_cell.block_flow.base.position.size.block = block_size; - - // Now we know the cell height, vertical align the cell's children. - child_table_cell.valign_children(); - - // Write in the size of the relative containing block for children. (This - // information is also needed to handle RTL.) - child_table_cell.block_flow.base.early_absolute_position_info = - EarlyAbsolutePositionInfo { - relative_containing_block_size: self.block_flow - .fragment - .content_box() - .size, - relative_containing_block_mode: self.block_flow - .fragment - .style() - .writing_mode, - }; + *col += 1; } } + // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of + // all cells). + let mut max_block_size = Au(0); + let mut largest_leftover_incoming_size = Au(0); + let thread_id = self.block_flow.base.thread_id; + let content_box = self.block_flow.base.position + - self.block_flow.fragment.border_padding + - self.block_flow.fragment.margin; - self.block_flow.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); + let mut col = 0; + for kid in self.block_flow.base.child_iter_mut() { + include_sizes_from_previous_rows(&mut col, &self.incoming_rowspan, + incoming_rowspan_data, &mut max_block_size, + &mut largest_leftover_incoming_size); + kid.place_float_if_applicable(); + debug_assert!(!kid.base().flags.is_float(), "table cells should never float"); + kid.assign_block_size_for_inorder_child_if_necessary(layout_context, + thread_id, + content_box); + + let row_span; + let column_span; + let cell_total; + { + let cell = kid.as_mut_table_cell(); + row_span = cell.row_span; + column_span = cell.column_span as usize; + cell_total = cell.total_block_size(); + } + let child_node = kid.mut_base(); + child_node.position.start.b = Au(0); + let mut cell_block_size_pressure = max(cell_total, child_node.position.size.block); + + if row_span > 1 { + if incoming_rowspan_data.len() <= col { + incoming_rowspan_data.resize(col + 1, Au(0)); + } + let border_sizes_spanned = get_spanned_border_size(border_info, row_index, row_span); + let pressure_copy = cell_block_size_pressure; + + cell_block_size_pressure -= border_sizes_spanned; + + // XXXManishearth in case this row covers more than cell_block_size_pressure / row_span + // anyway, we should use that to reduce the pressure on future rows. This will + // require an extra slow-path loop, sadly. + cell_block_size_pressure /= row_span as i32; + incoming_rowspan_data[col] = cell_block_size_pressure; + + // If this ends up being the last row, it needs to cover + // *all* this space + largest_leftover_incoming_size = max(largest_leftover_incoming_size, + pressure_copy); + } + + max_block_size = max(max_block_size, cell_block_size_pressure); + col += column_span; + } + include_sizes_from_previous_rows(&mut col, &self.incoming_rowspan, incoming_rowspan_data, &mut max_block_size, + &mut largest_leftover_incoming_size); + + let mut block_size = max_block_size; + // TODO: Percentage block-size + block_size = match MaybeAuto::from_style(self.block_flow + .fragment + .style() + .content_block_size(), + Au(0)) { + MaybeAuto::Auto => block_size, + MaybeAuto::Specified(value) => max(value, block_size), + }; + (block_size, largest_leftover_incoming_size) + } + + pub fn assign_block_size_to_self_and_children(&mut self, sizes: &[(Au, Au)], index: usize) { + // Assign the block-size of kid fragments, which is the same value as own block-size. + let block_size = sizes[index].0; + for kid in self.block_flow.base.child_iter_mut() { + let child_table_cell = kid.as_mut_table_cell(); + let block_size = if child_table_cell.row_span > 1 { + let row_sizes = sizes[index..].iter() + .take(child_table_cell.row_span as usize) + .fold(Au(0), |accum, size| accum + size.0); + let border_sizes_spanned = + get_spanned_border_size(sizes, index, child_table_cell.row_span); + row_sizes + border_sizes_spanned + } else { + block_size + }; + { + let kid_fragment = child_table_cell.mut_fragment(); + let mut position = kid_fragment.border_box; + position.size.block = block_size; + kid_fragment.border_box = position; + } + + // Assign the child's block size. + child_table_cell.block_flow.base.position.size.block = block_size; + + // Now we know the cell height, vertical align the cell's children. + child_table_cell.valign_children(); + + // Write in the size of the relative containing block for children. (This + // information is also needed to handle RTL.) + child_table_cell.block_flow.base.early_absolute_position_info = + EarlyAbsolutePositionInfo { + relative_containing_block_size: self.block_flow + .fragment + .content_box() + .size, + relative_containing_block_mode: self.block_flow + .fragment + .style() + .writing_mode, + }; + } + + // Assign the block-size of own fragment + let mut position = self.block_flow.fragment.border_box; + position.size.block = block_size; + self.block_flow.fragment.border_box = position; + self.block_flow.base.position.size.block = block_size; } pub fn populate_collapsed_border_spacing<'a, I>( @@ -222,6 +279,14 @@ impl TableRowFlow { } } +/// Given an array of (_, cumulative_border_size), the index of the +/// current row, and the >1 row_span of the cell, calculate the amount of +/// border-spacing spanned by the row +fn get_spanned_border_size(sizes: &[(Au, Au)], row_index: usize, row_span: u32) -> Au { + let last_row_idx = min(row_index + row_span as usize - 1, sizes.len() - 1); + sizes[last_row_idx].1 - sizes[row_index].1 +} + impl Flow for TableRowFlow { fn class(&self) -> FlowClass { FlowClass::TableRow @@ -449,9 +514,8 @@ impl Flow for TableRowFlow { }) } - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - debug!("assign_block_size: assigning block_size for table_row"); - self.assign_block_size_table_row_base(layout_context); + fn assign_block_size(&mut self, _: &LayoutContext) { + // the surrounding table or rowgroup does this } fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs index 49b0d427084..85bccb18ade 100644 --- a/components/layout/table_rowgroup.rs +++ b/components/layout/table_rowgroup.rs @@ -159,9 +159,9 @@ impl Flow for TableRowGroupFlow { }); } - fn assign_block_size(&mut self, _: &LayoutContext) { + fn assign_block_size(&mut self, lc: &LayoutContext) { debug!("assign_block_size: assigning block_size for table_rowgroup"); - self.block_flow.assign_block_size_for_table_like_flow(self.spacing.vertical()); + self.block_flow.assign_block_size_for_table_like_flow(self.spacing.vertical(), lc); } fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 2cd4e005039..f8e62ba7282 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -5837,6 +5837,18 @@ {} ] ], + "css/table_rowspan_notequal_a.html": [ + [ + "/_mozilla/css/table_rowspan_notequal_a.html", + [ + [ + "/_mozilla/css/table_rowspan_notequal_ref.html", + "!=" + ] + ], + {} + ] + ], "css/table_rowspan_rowgroup_a.html": [ [ "/_mozilla/css/table_rowspan_rowgroup_a.html", @@ -9756,6 +9768,11 @@ {} ] ], + "css/table_rowspan_notequal_ref.html": [ + [ + {} + ] + ], "css/table_rowspan_rowgroup_ref.html": [ [ {} @@ -63932,6 +63949,14 @@ "e045db1f738850e516079b66f915ba314b54ed99", "support" ], + "css/table_rowspan_notequal_a.html": [ + "ec7ab6318a498f6b88369f097af53a0626c6fb1d", + "reftest" + ], + "css/table_rowspan_notequal_ref.html": [ + "2a07facbd9f417264b46f6419ab00593051dae56", + "support" + ], "css/table_rowspan_rowgroup_a.html": [ "aadc4cdd09585330446dd231452c04810833f586", "reftest" diff --git a/tests/wpt/mozilla/meta/css/table_rowspan_rowgroup_a.html.ini b/tests/wpt/mozilla/meta/css/table_rowspan_rowgroup_a.html.ini new file mode 100644 index 00000000000..8a07107a556 --- /dev/null +++ b/tests/wpt/mozilla/meta/css/table_rowspan_rowgroup_a.html.ini @@ -0,0 +1,2 @@ +[table_rowspan_rowgroup_a.html] + expected: FAIL diff --git a/tests/wpt/mozilla/tests/css/table_rowspan_notequal_a.html b/tests/wpt/mozilla/tests/css/table_rowspan_notequal_a.html new file mode 100644 index 00000000000..f37ad51c0ec --- /dev/null +++ b/tests/wpt/mozilla/tests/css/table_rowspan_notequal_a.html @@ -0,0 +1,18 @@ + + + + + + + + + +
  
 
+ + + diff --git a/tests/wpt/mozilla/tests/css/table_rowspan_notequal_ref.html b/tests/wpt/mozilla/tests/css/table_rowspan_notequal_ref.html new file mode 100644 index 00000000000..2b1e1b6bbcd --- /dev/null +++ b/tests/wpt/mozilla/tests/css/table_rowspan_notequal_ref.html @@ -0,0 +1,17 @@ + + + + + + + + +
  
 
+ + +