servo: Merge #14518 - Fix inline layout of table cells impacted by rowspan (from mbrubeck:rowspan2); r=notriddle
authorMatt Brubeck <mbrubeck@limpet.net>
Mon, 26 Dec 2016 10:09:09 -0800
changeset 340425 8fd6f32ce95c5636911be74af879832ee7ab8893
parent 340424 2a4854cb07e3dc8ef9e8dee499704d7b1a62c96d
child 340426 72029e42297f8a40f102e144a76bd06aa2c803cf
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnotriddle
servo: Merge #14518 - Fix inline layout of table cells impacted by rowspan (from mbrubeck:rowspan2); r=notriddle This is part of the fix for #11297. This PR fixes the inline layout of table cells impacted by row-spanning cells from previous rows. A separate PR to follow will fix the table block size calculations to account for rowspan. This PR doesn't yet include any test changes. If it doesn't cause any existing tests to pass, I will add a new test to it. r? @pcwalton Source-Repo: https://github.com/servo/servo Source-Revision: 9d320d5a34fe9911266940eb1ce96204d345b678
servo/components/layout/table.rs
servo/components/layout/table_cell.rs
servo/components/layout/table_row.rs
servo/components/layout/table_rowgroup.rs
servo/components/layout/table_wrapper.rs
servo/components/script/dom/element.rs
servo/components/script/dom/htmltablecellelement.rs
servo/components/script/dom/webidls/HTMLTableCellElement.webidl
servo/components/script/layout_wrapper.rs
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/style/matching.rs
--- a/servo/components/layout/table.rs
+++ b/servo/components/layout/table.rs
@@ -89,17 +89,26 @@ impl TableFlow {
             child_cell_inline_sizes: &[CellIntrinsicInlineSize],
             surrounding_size: Au)
             -> IntrinsicISizes {
         let mut total_inline_sizes = IntrinsicISizes {
             minimum_inline_size: surrounding_size,
             preferred_inline_size: surrounding_size,
         };
         let mut column_index = 0;
+        let mut incoming_rowspan = vec![];
+
         for child_cell_inline_size in child_cell_inline_sizes {
+            // Skip any column occupied by a cell from a previous row.
+            while column_index < incoming_rowspan.len() && incoming_rowspan[column_index] != 1 {
+                if incoming_rowspan[column_index] > 1 {
+                    incoming_rowspan[column_index] -= 1;
+                }
+                column_index += 1;
+            }
             for _ in 0..child_cell_inline_size.column_span {
                 if column_index < parent_inline_sizes.len() {
                     // We already have some intrinsic size information for this column. Merge it in
                     // according to the rules specified in INTRINSIC § 4.
                     let parent_sizes = &mut parent_inline_sizes[column_index];
                     if child_cell_inline_size.column_span > 1 {
                         // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
                         // 4. For now we make this column contribute no width.
@@ -125,16 +134,24 @@ impl TableFlow {
                     }
                 }
 
                 total_inline_sizes.minimum_inline_size +=
                     parent_inline_sizes[column_index].minimum_length;
                 total_inline_sizes.preferred_inline_size +=
                     parent_inline_sizes[column_index].preferred;
 
+                // If this cell spans later rows, record its rowspan.
+                if child_cell_inline_size.row_span > 1 {
+                    if incoming_rowspan.len() < column_index + 1 {
+                        incoming_rowspan.resize(column_index + 1, 0);
+                    }
+                    incoming_rowspan[column_index] = child_cell_inline_size.row_span;
+                }
+
                 column_index += 1
             }
         }
 
         total_inline_sizes
     }
 
     /// Updates the minimum and preferred inline-size calculation for a single row. This is
@@ -413,31 +430,33 @@ impl Flow for TableFlow {
             }
         }
 
         let column_computed_inline_sizes = &self.column_computed_inline_sizes;
         let collapsed_inline_direction_border_widths_for_table =
             &self.collapsed_inline_direction_border_widths_for_table;
         let mut collapsed_block_direction_border_widths_for_table =
             self.collapsed_block_direction_border_widths_for_table.iter().peekable();
+        let mut incoming_rowspan = vec![];
         self.block_flow.propagate_assigned_inline_size_to_children(shared_context,
                                                                    inline_start_content_edge,
                                                                    inline_end_content_edge,
                                                                    content_inline_size,
                                                                    |child_flow,
                                                                     _child_index,
                                                                     _content_inline_size,
                                                                     writing_mode,
                                                                     _inline_start_margin_edge,
                                                                     _inline_end_margin_edge| {
             table_row::propagate_column_inline_sizes_to_child(
                 child_flow,
                 writing_mode,
                 column_computed_inline_sizes,
-                &spacing_per_cell);
+                &spacing_per_cell,
+                &mut incoming_rowspan);
             if child_flow.is_table_row() {
                 let child_table_row = child_flow.as_mut_table_row();
                 child_table_row.populate_collapsed_border_spacing(
                     collapsed_inline_direction_border_widths_for_table,
                     &mut collapsed_block_direction_border_widths_for_table);
             } else if child_flow.is_table_rowgroup() {
                 let child_table_rowgroup = child_flow.as_mut_table_rowgroup();
                 child_table_rowgroup.populate_collapsed_border_spacing(
@@ -642,16 +661,17 @@ impl<T> VecExt<T> for Vec<T> {
 /// only be called if border collapsing is on. It is factored out into a separate function
 /// because we process children of rowgroups too.
 fn perform_border_collapse_for_row(child_table_row: &mut TableRowFlow,
                                    table_inline_borders: &TableInlineCollapsedBorders,
                                    previous_block_borders: PreviousBlockCollapsedBorders,
                                    next_block_borders: NextBlockCollapsedBorders,
                                    inline_spacing: &mut Vec<Au>,
                                    block_spacing: &mut Vec<Au>) {
+    // TODO mbrubeck: Take rowspan and colspan into account.
     let number_of_borders_inline_direction = child_table_row.preliminary_collapsed_borders.inline.len();
     // Compute interior inline borders.
     for (i, this_inline_border) in child_table_row.preliminary_collapsed_borders
                                                   .inline
                                                   .iter_mut()
                                                   .enumerate() {
         child_table_row.final_collapsed_borders.inline.push_or_set(i, *this_inline_border);
         if i == 0 {
--- a/servo/components/layout/table_cell.rs
+++ b/servo/components/layout/table_cell.rs
@@ -36,37 +36,42 @@ pub struct TableCellFlow {
     pub block_flow: BlockFlow,
 
     /// Border collapse information for the cell.
     pub collapsed_borders: CollapsedBordersForCell,
 
     /// The column span of this cell.
     pub column_span: u32,
 
+    /// The rows spanned by this cell.
+    pub row_span: u32,
+
     /// Whether this cell is visible. If false, the value of `empty-cells` means that we must not
     /// display this cell.
     pub visible: bool,
 }
 
 impl TableCellFlow {
     pub fn from_fragment(fragment: Fragment) -> TableCellFlow {
         TableCellFlow {
             block_flow: BlockFlow::from_fragment(fragment),
             collapsed_borders: CollapsedBordersForCell::new(),
             column_span: 1,
+            row_span: 1,
             visible: true,
         }
     }
 
     pub fn from_node_fragment_and_visibility_flag<N: ThreadSafeLayoutNode>(
             node: &N, fragment: Fragment, visible: bool) -> TableCellFlow {
         TableCellFlow {
             block_flow: BlockFlow::from_fragment(fragment),
             collapsed_borders: CollapsedBordersForCell::new(),
             column_span: node.get_colspan(),
+            row_span: node.get_rowspan(),
             visible: visible,
         }
     }
 
     pub fn fragment(&mut self) -> &Fragment {
         &self.block_flow.fragment
     }
 
--- a/servo/components/layout/table_row.rs
+++ b/servo/components/layout/table_row.rs
@@ -40,16 +40,22 @@ pub struct TableRowFlow {
     pub block_flow: BlockFlow,
 
     /// Information about the intrinsic inline-sizes of each cell.
     pub cell_intrinsic_inline_sizes: Vec<CellIntrinsicInlineSize>,
 
     /// Information about the computed inline-sizes of each column.
     pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
 
+    /// The number of remaining rows spanned by cells in previous rows, indexed by column.
+    ///
+    /// Columns that are not included in this vector have the default rowspan of "1".  If there are
+    /// no cells with rowspan != 1 in previous rows, this vector may be empty.
+    pub incoming_rowspan: Vec<u32>,
+
     /// The spacing for this row, propagated down from the table during the inline-size assignment
     /// phase.
     pub spacing: border_spacing::T,
 
     /// The direction of the columns, propagated down from the table during the inline-size
     /// assignment phase.
     pub table_writing_mode: WritingMode,
 
@@ -73,26 +79,29 @@ impl Serialize for TableRowFlow {
 
 /// Information about the column inline size and span for each cell.
 #[derive(Serialize, Copy, Clone)]
 pub struct CellIntrinsicInlineSize {
     /// Inline sizes that this cell contributes to the column.
     pub column_size: ColumnIntrinsicInlineSize,
     /// The column span of this cell.
     pub column_span: u32,
+    /// The row span of this cell.
+    pub row_span: u32,
 }
 
 
 impl TableRowFlow {
     pub fn from_fragment(fragment: Fragment) -> TableRowFlow {
         let writing_mode = fragment.style().writing_mode;
         TableRowFlow {
             block_flow: BlockFlow::from_fragment(fragment),
             cell_intrinsic_inline_sizes: Vec::new(),
             column_computed_inline_sizes: Vec::new(),
+            incoming_rowspan: Vec::new(),
             spacing: border_spacing::T {
                 horizontal: Au(0),
                 vertical: Au(0),
             },
             table_writing_mode: writing_mode,
             preliminary_collapsed_borders: CollapsedBordersForRow::new(),
             final_collapsed_borders: CollapsedBordersForRow::new(),
             collapsed_border_spacing: CollapsedBorderSpacingForRow::new(),
@@ -263,23 +272,25 @@ impl Flow for TableRowFlow {
             let mut iterator = self.block_flow.base.child_iter_mut().enumerate().peekable();
             while let Some((i, kid)) = iterator.next() {
                 assert!(kid.is_table_cell());
 
                 // Collect the specified column inline-size of the cell. This is used in both
                 // fixed and automatic table layout calculation.
                 let child_specified_inline_size;
                 let child_column_span;
+                let child_row_span;
                 {
                     let child_table_cell = kid.as_mut_table_cell();
                     child_specified_inline_size = child_table_cell.block_flow
                                                                   .fragment
                                                                   .style
                                                                   .content_inline_size();
                     child_column_span = child_table_cell.column_span;
+                    child_row_span = child_table_cell.row_span;
 
                     // Perform border collapse if necessary.
                     if collapsing_borders {
                         perform_inline_direction_border_collapse_for_row(
                             row_style,
                             children_count,
                             i,
                             child_table_cell,
@@ -314,16 +325,17 @@ impl Flow for TableRowFlow {
                         LengthOrPercentageOrAuto::Percentage(_) => false,
                     },
                 };
                 min_inline_size = min_inline_size + child_column_inline_size.minimum_length;
                 pref_inline_size = pref_inline_size + child_column_inline_size.preferred;
                 self.cell_intrinsic_inline_sizes.push(CellIntrinsicInlineSize {
                     column_size: child_column_inline_size,
                     column_span: child_column_span,
+                    row_span: child_row_span,
                 });
             }
         }
 
         self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
         self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max(min_inline_size,
                                                                                 pref_inline_size);
     }
@@ -343,44 +355,57 @@ impl Flow for TableRowFlow {
         let inline_size_computer = InternalTable {
             border_collapse: self.block_flow.fragment.style.get_inheritedtable().border_collapse,
         };
         inline_size_computer.compute_used_inline_size(&mut self.block_flow,
                                                       shared_context,
                                                       containing_block_inline_size);
 
         // Spread out the completed inline sizes among columns with spans > 1.
-        let mut computed_inline_size_for_cells = Vec::new();
-        let mut column_computed_inline_size_iterator = self.column_computed_inline_sizes.iter();
+        let num_columns = self.column_computed_inline_sizes.len();
+        let mut computed_inline_size_for_cells = Vec::with_capacity(num_columns);
+        let mut col = 0;
+
         for cell_intrinsic_inline_size in &self.cell_intrinsic_inline_sizes {
+            // Skip any column occupied by a cell from a previous row.
+            while col < self.incoming_rowspan.len() && self.incoming_rowspan[col] != 1 {
+                let size = match self.column_computed_inline_sizes.get(col) {
+                    Some(column_computed_inline_size) => *column_computed_inline_size,
+                    None => ColumnComputedInlineSize { size: Au(0) } // See FIXME below.
+                };
+                computed_inline_size_for_cells.push(size);
+                col += 1;
+            }
             // Start with the computed inline size for the first column in the span.
             let mut column_computed_inline_size =
-                match column_computed_inline_size_iterator.next() {
+                match self.column_computed_inline_sizes.get(col) {
                     Some(column_computed_inline_size) => *column_computed_inline_size,
                     None => {
                         // We're in fixed layout mode and there are more cells in this row than
                         // columns we know about. According to CSS 2.1 § 17.5.2.1, the behavior is
                         // now undefined. So just use zero.
                         //
                         // FIXME(pcwalton): $10 says this isn't Web compatible.
                         ColumnComputedInlineSize {
                             size: Au(0),
                         }
                     }
                 };
+            col += 1;
 
             // Add in computed inline sizes for any extra columns in the span.
             for _ in 1..cell_intrinsic_inline_size.column_span {
                 let extra_column_computed_inline_size =
-                    match column_computed_inline_size_iterator.next() {
+                    match self.column_computed_inline_sizes.get(col) {
                         Some(column_computed_inline_size) => column_computed_inline_size,
                         None => break,
                     };
                 column_computed_inline_size.size = column_computed_inline_size.size +
                     extra_column_computed_inline_size.size + self.spacing.horizontal;
+                col += 1;
             }
 
             computed_inline_size_for_cells.push(column_computed_inline_size)
         }
 
         // Set up border collapse info.
         let border_collapse_info =
             match self.block_flow.fragment.style().get_inheritedtable().border_collapse {
@@ -392,29 +417,34 @@ impl Flow for TableRowFlow {
                 }
                 border_collapse::T::separate => None,
             };
 
         // Push those inline sizes down to the cells.
         let spacing = self.spacing;
         let row_writing_mode = self.block_flow.base.writing_mode;
         let table_writing_mode = self.table_writing_mode;
+        let incoming_rowspan = &self.incoming_rowspan;
+        let mut column_index = 0;
+
         self.block_flow.propagate_assigned_inline_size_to_children(shared_context,
                                                                    inline_start_content_edge,
                                                                    inline_end_content_edge,
                                                                    containing_block_inline_size,
                                                                    |child_flow,
                                                                     child_index,
                                                                     content_inline_size,
                                                                     _writing_mode,
                                                                     inline_start_margin_edge,
                                                                     inline_end_margin_edge| {
             set_inline_position_of_child_flow(
                 child_flow,
                 child_index,
+                &mut column_index,
+                incoming_rowspan,
                 row_writing_mode,
                 table_writing_mode,
                 &computed_inline_size_for_cells,
                 &spacing,
                 &border_collapse_info,
                 content_inline_size,
                 inline_start_margin_edge,
                 inline_end_margin_edge);
@@ -704,67 +734,111 @@ impl CollapsedBorder {
             (this_style, other_style) if this_style < other_style => *self = *other,
             // Step 4.
             _ if (self.provenance as i8) >= other.provenance as i8 => {}
             _ => *self = *other,
         }
     }
 }
 
-/// Pushes column inline size and border collapse info down to a child.
+/// Pushes column inline size, incoming rowspan, and border collapse info down to a child.
 pub fn propagate_column_inline_sizes_to_child(
         child_flow: &mut Flow,
         table_writing_mode: WritingMode,
         column_computed_inline_sizes: &[ColumnComputedInlineSize],
-        border_spacing: &border_spacing::T) {
-    // If the child is a row group or a row, the column inline-size info should be copied from its
+        border_spacing: &border_spacing::T,
+        incoming_rowspan: &mut Vec<u32>) {
+    // If the child is a row group or a row, the column inline-size and rowspan info should be copied from its
     // parent.
     //
     // FIXME(pcwalton): This seems inefficient. Reference count it instead?
     match child_flow.class() {
-        FlowClass::Table => {
-            let child_table_flow = child_flow.as_mut_table();
-            child_table_flow.column_computed_inline_sizes = column_computed_inline_sizes.to_vec();
-        }
         FlowClass::TableRowGroup => {
             let child_table_rowgroup_flow = child_flow.as_mut_table_rowgroup();
-            child_table_rowgroup_flow.column_computed_inline_sizes =
-                column_computed_inline_sizes.to_vec();
             child_table_rowgroup_flow.spacing = *border_spacing;
-            child_table_rowgroup_flow.table_writing_mode = table_writing_mode;
+            for kid in child_table_rowgroup_flow.block_flow.base.child_iter_mut() {
+                propagate_column_inline_sizes_to_child(kid,
+                                                       table_writing_mode,
+                                                       column_computed_inline_sizes,
+                                                       border_spacing,
+                                                       incoming_rowspan);
+            }
         }
         FlowClass::TableRow => {
             let child_table_row_flow = child_flow.as_mut_table_row();
             child_table_row_flow.column_computed_inline_sizes =
                 column_computed_inline_sizes.to_vec();
             child_table_row_flow.spacing = *border_spacing;
             child_table_row_flow.table_writing_mode = table_writing_mode;
+            child_table_row_flow.incoming_rowspan = incoming_rowspan.clone();
+
+            // Update the incoming rowspan for the next row.
+            let mut col = 0;
+            for cell in &child_table_row_flow.cell_intrinsic_inline_sizes {
+                // Skip any column occupied by a cell from a previous row.
+                while col < incoming_rowspan.len() && incoming_rowspan[col] != 1 {
+                    if incoming_rowspan[col] > 1 {
+                        incoming_rowspan[col] -= 1;
+                    }
+                    col += 1;
+                }
+                for _ in 0..cell.column_span {
+                    if col < incoming_rowspan.len() && incoming_rowspan[col] > 1 {
+                        incoming_rowspan[col] -= 1;
+                    }
+                    // If this cell spans later rows, record its rowspan.
+                    if cell.row_span != 1 {
+                        if incoming_rowspan.len() < col + 1 {
+                            incoming_rowspan.resize(col + 1, 1);
+                        }
+                        incoming_rowspan[col] = max(cell.row_span, incoming_rowspan[col]);
+                    }
+                    col += 1;
+                }
+            }
         }
         c => warn!("unexpected flow in table {:?}", c)
     }
 }
 
 /// Lay out table cells inline according to the computer column sizes.
 fn set_inline_position_of_child_flow(
         child_flow: &mut Flow,
         child_index: usize,
+        column_index: &mut usize,
+        incoming_rowspan: &[u32],
         row_writing_mode: WritingMode,
         table_writing_mode: WritingMode,
         column_computed_inline_sizes: &[ColumnComputedInlineSize],
         border_spacing: &border_spacing::T,
         border_collapse_info: &Option<BorderCollapseInfoForChildTableCell>,
         parent_content_inline_size: Au,
         inline_start_margin_edge: &mut Au,
         inline_end_margin_edge: &mut Au) {
     if !child_flow.is_table_cell() {
         return
     }
 
     let reverse_column_order = table_writing_mode.is_bidi_ltr() != row_writing_mode.is_bidi_ltr();
 
+    // Advance past any column occupied by a cell from a previous row.
+    while *column_index < incoming_rowspan.len() && incoming_rowspan[*column_index] != 1 {
+        let column_inline_size = column_computed_inline_sizes[*column_index].size;
+        let border_inline_size = match *border_collapse_info {
+            Some(_) => Au(0), // FIXME: Make collapsed borders account for colspan/rowspan.
+            None => border_spacing.horizontal,
+        };
+        if reverse_column_order {
+            *inline_end_margin_edge += column_inline_size + border_inline_size;
+        } else {
+            *inline_start_margin_edge += column_inline_size + border_inline_size;
+        }
+        *column_index += 1;
+    }
+
     // Handle border collapsing, if necessary.
     let child_table_cell = child_flow.as_mut_table_cell();
     match *border_collapse_info {
         Some(ref border_collapse_info) => {
             // Write in the child's border collapse state.
             child_table_cell.collapsed_borders = CollapsedBordersForCell {
                 inline_start_border: border_collapse_info.collapsed_borders_for_row
                                                          .inline
@@ -792,46 +866,46 @@ fn set_inline_position_of_child_flow(
                                                       .map_or(Au(0), |x| *x),
                 block_start_width: border_collapse_info.collapsed_border_spacing_for_row
                                                        .block_start,
                 block_end_width: border_collapse_info.collapsed_border_spacing_for_row.block_end,
             };
 
             // Move over past the collapsed border.
             if reverse_column_order {
-                *inline_end_margin_edge = *inline_end_margin_edge +
-                    child_table_cell.collapsed_borders.inline_start_width
+                *inline_end_margin_edge += child_table_cell.collapsed_borders.inline_start_width;
             } else {
-                *inline_start_margin_edge = *inline_start_margin_edge +
-                    child_table_cell.collapsed_borders.inline_start_width
+                *inline_start_margin_edge += child_table_cell.collapsed_borders.inline_start_width;
             }
         }
         None => {
             // Take spacing into account.
             if reverse_column_order {
-                *inline_end_margin_edge = *inline_end_margin_edge + border_spacing.horizontal
+                *inline_end_margin_edge += border_spacing.horizontal;
             } else {
-                *inline_start_margin_edge = *inline_start_margin_edge + border_spacing.horizontal
+                *inline_start_margin_edge += border_spacing.horizontal;
             }
         }
     }
 
-    let column_inline_size = column_computed_inline_sizes[child_index].size;
+    let column_inline_size = column_computed_inline_sizes[*column_index].size;
+    *column_index += 1;
+
     let kid_base = &mut child_table_cell.block_flow.base;
     kid_base.block_container_inline_size = column_inline_size;
 
     if reverse_column_order {
         // Columns begin from the inline-end edge.
         kid_base.position.start.i =
             parent_content_inline_size - *inline_end_margin_edge - column_inline_size;
-        *inline_end_margin_edge = *inline_end_margin_edge + column_inline_size;
+        *inline_end_margin_edge += column_inline_size;
     } else {
         // Columns begin from the inline-start edge.
         kid_base.position.start.i = *inline_start_margin_edge;
-        *inline_start_margin_edge = *inline_start_margin_edge + column_inline_size;
+        *inline_start_margin_edge += column_inline_size;
     }
 }
 
 #[derive(Copy, Clone)]
 pub struct BorderCollapseInfoForChildTableCell<'a> {
     collapsed_borders_for_row: &'a CollapsedBordersForRow,
     collapsed_border_spacing_for_row: &'a CollapsedBorderSpacingForRow,
 }
--- a/servo/components/layout/table_rowgroup.rs
+++ b/servo/components/layout/table_rowgroup.rs
@@ -18,39 +18,31 @@ use gfx_traits::ScrollRootId;
 use gfx_traits::print_tree::PrintTree;
 use layout_debug;
 use serde::{Serialize, Serializer};
 use std::fmt;
 use std::iter::{IntoIterator, Iterator, Peekable};
 use std::sync::Arc;
 use style::computed_values::{border_collapse, border_spacing};
 use style::context::SharedStyleContext;
-use style::logical_geometry::{LogicalSize, WritingMode};
+use style::logical_geometry::LogicalSize;
 use style::properties::ServoComputedValues;
-use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow};
-use table_row;
+use table::{ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow};
 
 /// A table formatting context.
 pub struct TableRowGroupFlow {
     /// Fields common to all block flows.
     pub block_flow: BlockFlow,
 
     /// Information about the intrinsic inline-sizes of each column.
     pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
 
-    /// Information about the actual inline sizes of each column.
-    pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
-
     /// The spacing for this rowgroup.
     pub spacing: border_spacing::T,
 
-    /// The direction of the columns, propagated down from the table during the inline-size
-    /// assignment phase.
-    pub table_writing_mode: WritingMode,
-
     /// The final width of the borders in the inline direction for each cell, computed by the
     /// entire table and pushed down into each row during inline size computation.
     pub collapsed_inline_direction_border_widths_for_table: Vec<Au>,
 
     /// The final width of the borders in the block direction for each cell, computed by the
     /// entire table and pushed down into each row during inline size computation.
     pub collapsed_block_direction_border_widths_for_table: Vec<Au>,
 }
@@ -58,26 +50,23 @@ pub struct TableRowGroupFlow {
 impl Serialize for TableRowGroupFlow {
     fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
         self.block_flow.serialize(serializer)
     }
 }
 
 impl TableRowGroupFlow {
     pub fn from_fragment(fragment: Fragment) -> TableRowGroupFlow {
-        let writing_mode = fragment.style().writing_mode;
         TableRowGroupFlow {
             block_flow: BlockFlow::from_fragment(fragment),
             column_intrinsic_inline_sizes: Vec::new(),
-            column_computed_inline_sizes: Vec::new(),
             spacing: border_spacing::T {
                 horizontal: Au(0),
                 vertical: Au(0),
             },
-            table_writing_mode: writing_mode,
             collapsed_inline_direction_border_widths_for_table: Vec::new(),
             collapsed_block_direction_border_widths_for_table: Vec::new(),
         }
     }
 
     pub fn populate_collapsed_border_spacing<'a, I>(
             &mut self,
             collapsed_inline_direction_border_widths_for_table: &[Au],
@@ -145,39 +134,30 @@ impl Flow for TableRowGroupFlow {
         let border_collapse = self.block_flow.fragment.style.get_inheritedtable().border_collapse;
         let inline_size_computer = InternalTable {
             border_collapse: border_collapse,
         };
         inline_size_computer.compute_used_inline_size(&mut self.block_flow,
                                                       shared_context,
                                                       containing_block_inline_size);
 
-        let column_computed_inline_sizes = &self.column_computed_inline_sizes;
-        let border_spacing = self.spacing;
-        let table_writing_mode = self.table_writing_mode;
         let collapsed_inline_direction_border_widths_for_table =
             &self.collapsed_inline_direction_border_widths_for_table;
         let mut collapsed_block_direction_border_widths_for_table =
             self.collapsed_block_direction_border_widths_for_table.iter().peekable();
         self.block_flow.propagate_assigned_inline_size_to_children(shared_context,
                                                                    inline_start_content_edge,
                                                                    inline_end_content_edge,
                                                                    content_inline_size,
                                                                    |child_flow,
                                                                     _child_index,
                                                                     _content_inline_size,
                                                                     _writing_mode,
                                                                     _inline_start_margin_edge,
                                                                     _inline_end_margin_edge| {
-            table_row::propagate_column_inline_sizes_to_child(
-                child_flow,
-                table_writing_mode,
-                column_computed_inline_sizes,
-                &border_spacing);
-
             if border_collapse == border_collapse::T::collapse {
                 let child_table_row = child_flow.as_mut_table_row();
                 child_table_row.populate_collapsed_border_spacing(
                     collapsed_inline_direction_border_widths_for_table,
                     &mut collapsed_block_direction_border_widths_for_table);
             }
         });
     }
--- a/servo/components/layout/table_wrapper.rs
+++ b/servo/components/layout/table_wrapper.rs
@@ -32,17 +32,16 @@ use std::ops::Add;
 use std::sync::Arc;
 use style::computed_values::{border_collapse, position, table_layout};
 use style::context::SharedStyleContext;
 use style::logical_geometry::{LogicalRect, LogicalSize};
 use style::properties::ServoComputedValues;
 use style::values::CSSFloat;
 use style::values::computed::LengthOrPercentageOrAuto;
 use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
-use table_row;
 
 #[derive(Copy, Clone, Serialize, Debug)]
 pub enum TableLayout {
     Fixed,
     Auto
 }
 
 /// A table wrapper flow based on a block formatting context.
@@ -389,43 +388,36 @@ impl Flow for TableWrapperFlow {
                 Some(intermediate_column_inline_sizes.iter().map(|sizes| {
                     ColumnComputedInlineSize {
                         size: sizes.size,
                     }
                 }).collect::<Vec<_>>())
             }
         };
 
-        let border_spacing = self.block_flow.fragment.style().get_inheritedtable().border_spacing;
         match assigned_column_inline_sizes {
             None => {
                 self.block_flow
                     .propagate_assigned_inline_size_to_children(shared_context,
                                                                 inline_start_content_edge,
                                                                 inline_end_content_edge,
                                                                 content_inline_size,
                                                                 |_, _, _, _, _, _| {})
             }
             Some(ref assigned_column_inline_sizes) => {
                 self.block_flow
                     .propagate_assigned_inline_size_to_children(shared_context,
                                                                 inline_start_content_edge,
                                                                 inline_end_content_edge,
                                                                 content_inline_size,
-                                                                |child_flow,
-                                                                 _child_index,
-                                                                 _content_inline_size,
-                                                                 writing_mode,
-                                                                 _inline_start_margin_edge,
-                                                                 _inline_end_margin_edge| {
-                    table_row::propagate_column_inline_sizes_to_child(
-                        child_flow,
-                        writing_mode,
-                        assigned_column_inline_sizes,
-                        &border_spacing);
+                                                                |child_flow, _, _, _, _, _| {
+                    if child_flow.class() == FlowClass::Table {
+                        child_flow.as_mut_table().column_computed_inline_sizes =
+                            assigned_column_inline_sizes.to_vec();
+                    }
                 })
             }
         }
 
     }
 
     fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
         debug!("assign_block_size: assigning block_size for table_wrapper");
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -320,16 +320,18 @@ pub trait LayoutElementHelpers {
     unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>;
 
     #[allow(unsafe_code)]
     unsafe fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, &mut V)
         where V: Push<ApplicableDeclarationBlock>;
     #[allow(unsafe_code)]
     unsafe fn get_colspan(self) -> u32;
     #[allow(unsafe_code)]
+    unsafe fn get_rowspan(self) -> u32;
+    #[allow(unsafe_code)]
     unsafe fn html_element_in_html_document_for_layout(&self) -> bool;
     fn id_attribute(&self) -> *const Option<Atom>;
     fn style_attribute(&self) -> *const Option<Arc<RwLock<PropertyDeclarationBlock>>>;
     fn local_name(&self) -> &LocalName;
     fn namespace(&self) -> &Namespace;
     fn get_checked_state_for_layout(&self) -> bool;
     fn get_indeterminate_state_for_layout(&self) -> bool;
     fn get_state_for_layout(&self) -> ElementState;
@@ -623,16 +625,27 @@ impl LayoutElementHelpers for LayoutJS<E
             this.get_colspan().unwrap_or(1)
         } else {
             // Don't panic since `display` can cause this to be called on arbitrary
             // elements.
             1
         }
     }
 
+    #[allow(unsafe_code)]
+    unsafe fn get_rowspan(self) -> u32 {
+        if let Some(this) = self.downcast::<HTMLTableCellElement>() {
+            this.get_rowspan().unwrap_or(1)
+        } else {
+            // Don't panic since `display` can cause this to be called on arbitrary
+            // elements.
+            1
+        }
+    }
+
     #[inline]
     #[allow(unsafe_code)]
     unsafe fn html_element_in_html_document_for_layout(&self) -> bool {
         if (*self.unsafe_get()).namespace != ns!(html) {
             return false;
         }
         self.upcast::<Node>().owner_doc_for_layout().is_html_document_for_layout()
     }
--- a/servo/components/script/dom/htmltablecellelement.rs
+++ b/servo/components/script/dom/htmltablecellelement.rs
@@ -13,16 +13,17 @@ use dom::element::{Element, RawLayoutEle
 use dom::htmlelement::HTMLElement;
 use dom::htmltablerowelement::HTMLTableRowElement;
 use dom::node::Node;
 use dom::virtualmethods::VirtualMethods;
 use html5ever_atoms::LocalName;
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
 
 const DEFAULT_COLSPAN: u32 = 1;
+const DEFAULT_ROWSPAN: u32 = 1;
 
 #[dom_struct]
 pub struct HTMLTableCellElement {
     htmlelement: HTMLElement,
 }
 
 impl HTMLTableCellElement {
     pub fn new_inherited(tag_name: LocalName,
@@ -37,16 +38,22 @@ impl HTMLTableCellElement {
 
 impl HTMLTableCellElementMethods for HTMLTableCellElement {
     // https://html.spec.whatwg.org/multipage/#dom-tdth-colspan
     make_uint_getter!(ColSpan, "colspan", DEFAULT_COLSPAN);
 
     // https://html.spec.whatwg.org/multipage/#dom-tdth-colspan
     make_uint_setter!(SetColSpan, "colspan", DEFAULT_COLSPAN);
 
+    // https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan
+    make_uint_getter!(RowSpan, "rowspan", DEFAULT_ROWSPAN);
+
+    // https://html.spec.whatwg.org/multipage/#dom-tdth-rowspan
+    make_uint_setter!(SetRowSpan, "rowspan", DEFAULT_ROWSPAN);
+
     // https://html.spec.whatwg.org/multipage/#dom-tdth-bgcolor
     make_getter!(BgColor, "bgcolor");
 
     // https://html.spec.whatwg.org/multipage/#dom-tdth-bgcolor
     make_legacy_color_setter!(SetBgColor, "bgcolor");
 
     // https://html.spec.whatwg.org/multipage/#dom-tdth-width
     make_getter!(Width, "width");
@@ -70,16 +77,17 @@ impl HTMLTableCellElementMethods for HTM
                        .map_or(-1, |p| p as i32)
     }
 }
 
 
 pub trait HTMLTableCellElementLayoutHelpers {
     fn get_background_color(&self) -> Option<RGBA>;
     fn get_colspan(&self) -> Option<u32>;
+    fn get_rowspan(&self) -> Option<u32>;
     fn get_width(&self) -> LengthOrPercentageOrAuto;
 }
 
 #[allow(unsafe_code)]
 impl HTMLTableCellElementLayoutHelpers for LayoutJS<HTMLTableCellElement> {
     fn get_background_color(&self) -> Option<RGBA> {
         unsafe {
             (&*self.upcast::<Element>().unsafe_get())
@@ -92,16 +100,24 @@ impl HTMLTableCellElementLayoutHelpers f
     fn get_colspan(&self) -> Option<u32> {
         unsafe {
             (&*self.upcast::<Element>().unsafe_get())
                 .get_attr_for_layout(&ns!(), &local_name!("colspan"))
                 .map(AttrValue::as_uint)
         }
     }
 
+    fn get_rowspan(&self) -> Option<u32> {
+        unsafe {
+            (&*self.upcast::<Element>().unsafe_get())
+                .get_attr_for_layout(&ns!(), &local_name!("rowspan"))
+                .map(AttrValue::as_uint)
+        }
+    }
+
     fn get_width(&self) -> LengthOrPercentageOrAuto {
         unsafe {
             (&*self.upcast::<Element>().unsafe_get())
                 .get_attr_for_layout(&ns!(), &local_name!("width"))
                 .map(AttrValue::as_dimension)
                 .cloned()
                 .unwrap_or(LengthOrPercentageOrAuto::Auto)
         }
@@ -111,14 +127,15 @@ impl HTMLTableCellElementLayoutHelpers f
 impl VirtualMethods for HTMLTableCellElement {
     fn super_type(&self) -> Option<&VirtualMethods> {
         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
     }
 
     fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
         match *local_name {
             local_name!("colspan") => AttrValue::from_u32(value.into(), DEFAULT_COLSPAN),
+            local_name!("rowspan") => AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN),
             local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
             local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
             _ => self.super_type().unwrap().parse_plain_attribute(local_name, value),
         }
     }
 }
--- a/servo/components/script/dom/webidls/HTMLTableCellElement.webidl
+++ b/servo/components/script/dom/webidls/HTMLTableCellElement.webidl
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://html.spec.whatwg.org/multipage/#htmltablecellelement
 [Abstract]
 interface HTMLTableCellElement : HTMLElement {
-             attribute unsigned long colSpan;
-  //         attribute unsigned long rowSpan;
+  attribute unsigned long colSpan;
+  attribute unsigned long rowSpan;
   //         attribute DOMString headers;
   readonly attribute long cellIndex;
 
   // also has obsolete members
 };
 
 // https://html.spec.whatwg.org/multipage/#HTMLTableCellElement-partial
 partial interface HTMLTableCellElement {
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -859,16 +859,22 @@ impl<'ln> ThreadSafeLayoutNode for Servo
         this.iframe_pipeline_id()
     }
 
     fn get_colspan(&self) -> u32 {
         unsafe {
             self.get_jsmanaged().downcast::<Element>().unwrap().get_colspan()
         }
     }
+
+    fn get_rowspan(&self) -> u32 {
+        unsafe {
+            self.get_jsmanaged().downcast::<Element>().unwrap().get_rowspan()
+        }
+    }
 }
 
 pub struct ThreadSafeLayoutNodeChildrenIterator<ConcreteNode: ThreadSafeLayoutNode> {
     current_node: Option<ConcreteNode>,
     parent_node: ConcreteNode,
 }
 
 impl<ConcreteNode> ThreadSafeLayoutNodeChildrenIterator<ConcreteNode>
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -260,16 +260,18 @@ pub trait ThreadSafeLayoutNode: Clone + 
     fn svg_data(&self) -> Option<SVGSVGData>;
 
     /// If this node is an iframe element, returns its pipeline ID. If this node is
     /// not an iframe element, fails.
     fn iframe_pipeline_id(&self) -> PipelineId;
 
     fn get_colspan(&self) -> u32;
 
+    fn get_rowspan(&self) -> u32;
+
     fn fragment_type(&self) -> FragmentType {
         match self.get_pseudo_element_type() {
             PseudoElementType::Normal => FragmentType::FragmentBody,
             PseudoElementType::Before(_) => FragmentType::BeforePseudoContent,
             PseudoElementType::After(_) => FragmentType::AfterPseudoContent,
             PseudoElementType::DetailsSummary(_) => FragmentType::FragmentBody,
             PseudoElementType::DetailsContent(_) => FragmentType::FragmentBody,
         }
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -258,18 +258,18 @@ pub fn common_style_affecting_attributes
             mode: CommonStyleAffectingAttributeMode::IsEqual(atom!("right"), ALIGN_RIGHT_ATTRIBUTE),
         }
     ]
 }
 
 /// Attributes that, if present, disable style sharing. All legacy HTML attributes must be in
 /// either this list or `common_style_affecting_attributes`. See the comment in
 /// `synthesize_presentational_hints_for_legacy_attributes`.
-pub fn rare_style_affecting_attributes() -> [LocalName; 3] {
-    [ local_name!("bgcolor"), local_name!("border"), local_name!("colspan") ]
+pub fn rare_style_affecting_attributes() -> [LocalName; 4] {
+    [local_name!("bgcolor"), local_name!("border"), local_name!("colspan"), local_name!("rowspan")]
 }
 
 fn have_same_class<E: TElement>(element: &E,
                                 candidate: &mut StyleSharingCandidate<E>,
                                 candidate_element: &E) -> bool {
     // XXX Efficiency here, I'm only validating ideas.
     let mut element_class_attributes = vec![];
     element.each_class(|c| element_class_attributes.push(c.clone()));