Bug 35168 (Part 2) - Allow relative positioning of internal table objects. r=dbaron
authorSeth Fowler <seth@mozilla.com>
Tue, 19 Aug 2014 18:24:58 -0700
changeset 246996 853447a587aa3059dfec85f9b45e169c725e61cf
parent 246995 bce28de2368a5c06e401f72ac300798e904f27c2
child 246997 c8fd5af3c60c30df11c86d07276510034b244551
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs35168
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 35168 (Part 2) - Allow relative positioning of internal table objects. r=dbaron
layout/base/RestyleManager.cpp
layout/tables/nsTableFrame.cpp
layout/tables/nsTableFrame.h
layout/tables/nsTablePainter.cpp
layout/tables/nsTablePainter.h
layout/tables/nsTableRowFrame.cpp
layout/tables/nsTableRowGroupFrame.cpp
layout/tables/nsTableRowGroupFrame.h
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -405,26 +405,24 @@ RestyleManager::RecomputePosition(nsIFra
     StyleChangeReflow(aFrame, nsChangeHint_NeedReflow);
     return false;
   }
 
   aFrame->SchedulePaint();
 
   // For relative positioning, we can simply update the frame rect
   if (display->IsRelativelyPositionedStyle()) {
-    if (display->IsInnerTableStyle()) {
-      // We don't currently support relative positioning of inner table
-      // elements (bug 35168).  If we apply offsets to things we haven't
-      // previously offset, we'll get confused.  So bail.
-      return true;
-    }
-
-
     // Move the frame
     if (display->mPosition == NS_STYLE_POSITION_STICKY) {
+      if (display->IsInnerTableStyle()) {
+        // We don't currently support sticky positioning of inner table
+        // elements (bug 975644). Bail.
+        return true;
+      }
+
       // Update sticky positioning for an entire element at once, starting with
       // the first continuation or ib-split sibling.
       // It's rare that the frame we already have isn't already the first
       // continuation or ib-split sibling, but it can happen when styles differ
       // across continuations such as ::first-line or ::first-letter, and in
       // those cases we will generally (but maybe not always) do the work twice.
       nsIFrame *firstContinuation =
         nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1868,17 +1868,17 @@ nsTableFrame::Reflow(nsPresContext*     
 
       ReflowTable(aDesiredSize, aReflowState, aReflowState.AvailableHeight(),
                   lastChildReflowed, aStatus);
 
       if (lastChildReflowed && NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
         // if there is an incomplete child, then set the desired height to include it but not the next one
         nsMargin borderPadding = GetChildAreaOffset(&aReflowState);
         aDesiredSize.Height() = borderPadding.bottom + GetCellSpacingY(GetRowCount()) +
-                              lastChildReflowed->GetRect().YMost();
+                              lastChildReflowed->GetNormalRect().YMost();
       }
       haveDesiredHeight = true;
 
       mutable_rs.mFlags.mSpecialHeightReflow = false;
     }
   }
   else {
     // Calculate the overflow area contribution from our children.
@@ -2672,26 +2672,27 @@ nsTableFrame::InitChildReflowState(nsHTM
     aReflowState.SetHResize(true);
   }
 }
 
 // Position and size aKidFrame and update our reflow state. The origin of
 // aKidRect is relative to the upper-left origin of our frame
 void nsTableFrame::PlaceChild(nsTableReflowState&  aReflowState,
                               nsIFrame*            aKidFrame,
+                              nsPoint              aKidPosition,
                               nsHTMLReflowMetrics& aKidDesiredSize,
                               const nsRect&        aOriginalKidRect,
                               const nsRect&        aOriginalKidVisualOverflow)
 {
   bool isFirstReflow =
     (aKidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0;
 
   // Place and size the child
   FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr,
-                    aReflowState.x, aReflowState.y, 0);
+                    aKidPosition.x, aKidPosition.y, 0);
 
   InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow,
                        isFirstReflow);
 
   // Adjust the running y-offset
   aReflowState.y += aKidDesiredSize.Height();
 
   // If our height is constrained, then update the available height
@@ -2865,17 +2866,20 @@ nsTableFrame::PlaceRepeatedFooter(nsTabl
   nsRect origTfootRect = aTfoot->GetRect();
   nsRect origTfootVisualOverflow = aTfoot->GetVisualOverflowRect();
           
   nsReflowStatus footerStatus;
   nsHTMLReflowMetrics desiredSize(aReflowState.reflowState);
   desiredSize.ClearSize();
   ReflowChild(aTfoot, presContext, desiredSize, footerReflowState,
               aReflowState.x, aReflowState.y, 0, footerStatus);
-  PlaceChild(aReflowState, aTfoot, desiredSize, origTfootRect,
+  nsPoint kidPosition(aReflowState.x, aReflowState.y);
+  footerReflowState.ApplyRelativePositioning(&kidPosition);
+
+  PlaceChild(aReflowState, aTfoot, kidPosition, desiredSize, origTfootRect,
              origTfootVisualOverflow);
 }
                     
 // Reflow the children based on the avail size and reason in aReflowState
 // update aReflowMetrics a aStatus
 void
 nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState,
                              nsReflowStatus&     aStatus,
@@ -2985,31 +2989,33 @@ nsTableFrame::ReflowChildren(nsTableRefl
                                        nsHTMLReflowState::CALLER_WILL_INIT);
       InitChildReflowState(kidReflowState);
 
       // If this isn't the first row group, and the previous row group has a
       // nonzero YMost, then we can't be at the top of the page.
       // We ignore a repeated head row group in this check to avoid causing
       // infinite loops in some circumstances - see bug 344883.
       if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) &&
-          (rowGroups[childX - 1]->GetRect().YMost() > 0)) {
+          (rowGroups[childX - 1]->GetNormalRect().YMost() > 0)) {
         kidReflowState.mFlags.mIsTopOfPage = false;
       }
       aReflowState.y += cellSpacingY;
       if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) {
         aReflowState.availSize.height -= cellSpacingY;
       }
       // record the presence of a next in flow, it might get destroyed so we
       // need to reorder the row group array
       bool reorder = false;
       if (kidFrame->GetNextInFlow())
         reorder = true;
 
       ReflowChild(kidFrame, presContext, desiredSize, kidReflowState,
                   aReflowState.x, aReflowState.y, 0, aStatus);
+      nsPoint kidPosition(aReflowState.x, aReflowState.y);
+      kidReflowState.ApplyRelativePositioning(&kidPosition);
 
       if (reorder) {
         // reorder row groups the reflow may have changed the nextinflows
         OrderRowGroups(rowGroups, &thead, &tfoot);
         childX = rowGroups.IndexOf(kidFrame);
         if (childX == RowGroupArray::NoIndex) {
           // XXXbz can this happen?
           childX = rowGroups.Length();
@@ -3031,18 +3037,18 @@ nsTableFrame::ReflowChildren(nsTableRefl
           aStatus = NS_INLINE_LINE_BREAK_BEFORE();
           break;
         }
         // if we are on top of the page place with dataloss
         if (kidReflowState.mFlags.mIsTopOfPage) {
           if (childX+1 < rowGroups.Length()) {
             nsIFrame* nextRowGroupFrame = rowGroups[childX + 1];
             if (nextRowGroupFrame) {
-              PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect,
-                         oldKidVisualOverflow);
+              PlaceChild(aReflowState, kidFrame, kidPosition, desiredSize,
+                         oldKidRect, oldKidVisualOverflow);
               if (allowRepeatedFooter) {
                 PlaceRepeatedFooter(aReflowState, tfoot, footerHeight);
               }
               else if (tfoot && tfoot->IsRepeatable()) {
                 tfoot->SetRepeatable(false);
               }
               aStatus = NS_FRAME_NOT_COMPLETE;
               PushChildren(rowGroups, childX + 1);
@@ -3060,18 +3066,18 @@ nsTableFrame::ReflowChildren(nsTableRefl
               tfoot->SetRepeatable(false);
             }
             aStatus = NS_FRAME_NOT_COMPLETE;
             PushChildren(rowGroups, childX);
             aLastChildReflowed = prevKidFrame;
             break;
           }
           else { // we can't push so lets make clear how much space we need
-            PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect,
-                                     oldKidVisualOverflow);
+            PlaceChild(aReflowState, kidFrame, kidPosition, desiredSize,
+                       oldKidRect, oldKidVisualOverflow);
             aLastChildReflowed = kidFrame;
             if (allowRepeatedFooter) {
               PlaceRepeatedFooter(aReflowState, tfoot, footerHeight);
               aLastChildReflowed = tfoot;
             }
             break;
           }
         }
@@ -3084,17 +3090,17 @@ nsTableFrame::ReflowChildren(nsTableRefl
       if (NS_FRAME_IS_COMPLETE(aStatus) && isPaginated &&
           (NS_UNCONSTRAINEDSIZE != kidReflowState.AvailableHeight())) {
         nsIFrame* nextKid =
           (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr;
         pageBreak = PageBreakAfter(kidFrame, nextKid);
       }
 
       // Place the child
-      PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect,
+      PlaceChild(aReflowState, kidFrame, kidPosition, desiredSize, oldKidRect,
                  oldKidVisualOverflow);
 
       // Remember where we just were in case we end up pushing children
       prevKidFrame = kidFrame;
 
       // Special handling for incomplete children
       if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
         nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();
@@ -3131,22 +3137,22 @@ nsTableFrame::ReflowChildren(nsTableRefl
         if (nextSibling) {
           PushChildren(rowGroups, childX + 1);
         }
         break;
       }
     }
     else { // it isn't being reflowed
       aReflowState.y += cellSpacingY;
-      nsRect kidRect = kidFrame->GetRect();
+      nsRect kidRect = kidFrame->GetNormalRect();
       if (kidRect.y != aReflowState.y) {
         // invalidate the old position
         kidFrame->InvalidateFrameSubtree();
-        kidRect.y = aReflowState.y;
-        kidFrame->SetRect(kidRect);        // move to the new position
+        // move to the new position
+        kidFrame->MovePositionBy(nsPoint(0, aReflowState.y - kidRect.y));
         RePositionViews(kidFrame);
         // invalidate the new position
         kidFrame->InvalidateFrameSubtree();
       }
       aReflowState.y += kidRect.height;
 
       // If our height is constrained then update the available height.
       if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) {
@@ -3289,74 +3295,74 @@ nsTableFrame::DistributeHeightToRows(con
   nscoord pctBasis = aReflowState.ComputedHeight() - GetCellSpacingY(-1, GetRowCount());
   nscoord yOriginRG = borderPadding.top + GetCellSpacingY(0);
   nscoord yEndRG = yOriginRG;
   uint32_t rgX;
   for (rgX = 0; rgX < rowGroups.Length(); rgX++) {
     nsTableRowGroupFrame* rgFrame = rowGroups[rgX];
     nscoord amountUsedByRG = 0;
     nscoord yOriginRow = 0;
-    nsRect rgRect = rgFrame->GetRect();
+    nsRect rgNormalRect = rgFrame->GetNormalRect();
     if (!rgFrame->HasStyleHeight()) {
       nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
       while (rowFrame) {
-        nsRect rowRect = rowFrame->GetRect();
+        nsRect rowNormalRect = rowFrame->GetNormalRect();
         nscoord cellSpacingY = GetCellSpacingY(rowFrame->GetRowIndex());
         if ((amountUsed < aAmount) && rowFrame->HasPctHeight()) {
           nscoord pctHeight = rowFrame->GetHeight(pctBasis);
-          nscoord amountForRow = std::min(aAmount - amountUsed, pctHeight - rowRect.height);
+          nscoord amountForRow = std::min(aAmount - amountUsed,
+                                          pctHeight - rowNormalRect.height);
           if (amountForRow > 0) {
-            nsRect oldRowRect = rowRect;
-            rowRect.height += amountForRow;
-            // XXXbz we don't need to change rowRect.y to be yOriginRow?
-            rowFrame->SetRect(rowRect);
-            yOriginRow += rowRect.height + cellSpacingY;
-            yEndRG += rowRect.height + cellSpacingY;
+            // XXXbz we don't need to move the row's y position to yOriginRow?
+            nsRect origRowRect = rowFrame->GetRect();
+            nscoord newRowHeight = rowNormalRect.height + amountForRow;
+            rowFrame->SetSize(nsSize(rowNormalRect.width, newRowHeight));
+            yOriginRow += newRowHeight + cellSpacingY;
+            yEndRG += newRowHeight + cellSpacingY;
             amountUsed += amountForRow;
             amountUsedByRG += amountForRow;
             //rowFrame->DidResize();
             nsTableFrame::RePositionViews(rowFrame);
 
-            rgFrame->InvalidateFrameWithRect(oldRowRect);
+            rgFrame->InvalidateFrameWithRect(origRowRect);
             rgFrame->InvalidateFrame();
           }
         }
         else {
-          if (amountUsed > 0 && yOriginRow != rowRect.y &&
+          if (amountUsed > 0 && yOriginRow != rowNormalRect.y &&
               !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
             rowFrame->InvalidateFrameSubtree();
-            rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow));
+            rowFrame->MovePositionBy(nsPoint(0, yOriginRow - rowNormalRect.y));
             nsTableFrame::RePositionViews(rowFrame);
             rowFrame->InvalidateFrameSubtree();
           }
-          yOriginRow += rowRect.height + cellSpacingY;
-          yEndRG += rowRect.height + cellSpacingY;
+          yOriginRow += rowNormalRect.height + cellSpacingY;
+          yEndRG += rowNormalRect.height + cellSpacingY;
         }
         rowFrame = rowFrame->GetNextRow();
       }
       if (amountUsed > 0) {
-        if (rgRect.y != yOriginRG) {
+        if (rgNormalRect.y != yOriginRG) {
           rgFrame->InvalidateFrameSubtree();
         }
 
-        nsRect origRgRect = rgRect;
+        nsRect origRgNormalRect = rgFrame->GetRect();
         nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect();
 
-        rgRect.y = yOriginRG;
-        rgRect.height += amountUsedByRG;
-
-        rgFrame->SetRect(rgRect);
-
-        nsTableFrame::InvalidateTableFrame(rgFrame, origRgRect,
+        rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y));
+        rgFrame->SetSize(nsSize(rgNormalRect.width,
+                                rgNormalRect.height + amountUsedByRG));
+
+        nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
                                            origRgVisualOverflow, false);
       }
     }
-    else if (amountUsed > 0 && yOriginRG != rgRect.y) {
+    else if (amountUsed > 0 && yOriginRG != rgNormalRect.y) {
       rgFrame->InvalidateFrameSubtree();
-      rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG));
+      rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y));
       // Make sure child views are properly positioned
       nsTableFrame::RePositionViews(rgFrame);
       rgFrame->InvalidateFrameSubtree();
     }
     yOriginRG = yEndRG;
   }
 
   if (amountUsed >= aAmount) {
@@ -3426,97 +3432,100 @@ nsTableFrame::DistributeHeightToRows(con
   // allocate the extra height to the unstyled row groups and rows
   nscoord heightToDistribute = aAmount - amountUsed;
   yOriginRG = borderPadding.top + GetCellSpacingY(-1);
   yEndRG = yOriginRG;
   for (rgX = 0; rgX < rowGroups.Length(); rgX++) {
     nsTableRowGroupFrame* rgFrame = rowGroups[rgX];
     nscoord amountUsedByRG = 0;
     nscoord yOriginRow = 0;
-    nsRect rgRect = rgFrame->GetRect();
+    nsRect rgNormalRect = rgFrame->GetNormalRect();
     nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect();
     // see if there is an eligible row group or we distribute to all rows
     if (!firstUnStyledRG || !rgFrame->HasStyleHeight() || !eligibleRows) {
       nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
       while (rowFrame) {
         nscoord cellSpacingY = GetCellSpacingY(rowFrame->GetRowIndex());
-        nsRect rowRect = rowFrame->GetRect();
+        nsRect rowNormalRect = rowFrame->GetNormalRect();
         nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect();
         // see if there is an eligible row or we distribute to all rows
         if (!firstUnStyledRow || !rowFrame->HasStyleHeight() || !eligibleRows) {
           float ratio;
           if (eligibleRows) {
             if (!expandEmptyRows) {
               // The amount of additional space each row gets is proportional to
               // its height
-              ratio = float(rowRect.height) / float(divisor);
+              ratio = float(rowNormalRect.height) / float(divisor);
             } else {
               // empty rows get all the same additional space
               ratio = 1.0f / float(eligibleRows);
             }
           }
           else {
             // all rows get the same additional space
             ratio = 1.0f / float(divisor);
           }
           // give rows their additional space, except for the last row which
           // gets the remainder
           nscoord amountForRow = (rowFrame == lastEligibleRow)
                                  ? aAmount - amountUsed : NSToCoordRound(((float)(heightToDistribute)) * ratio);
           amountForRow = std::min(amountForRow, aAmount - amountUsed);
 
-          if (yOriginRow != rowRect.y) {
+          if (yOriginRow != rowNormalRect.y) {
             rowFrame->InvalidateFrameSubtree();
           }
 
           // update the row height
-          nsRect newRowRect(rowRect.x, yOriginRow, rowRect.width,
-                            rowRect.height + amountForRow);
-          rowFrame->SetRect(newRowRect);
-
-          yOriginRow += newRowRect.height + cellSpacingY;
-          yEndRG += newRowRect.height + cellSpacingY;
+          nsRect origRowRect = rowFrame->GetRect();
+          nscoord newRowHeight = rowNormalRect.height + amountForRow;
+          rowFrame->MovePositionBy(nsPoint(0, yOriginRow - rowNormalRect.y));
+          rowFrame->SetSize(nsSize(rowNormalRect.width, newRowHeight));
+
+          yOriginRow += newRowHeight + cellSpacingY;
+          yEndRG += newRowHeight + cellSpacingY;
 
           amountUsed += amountForRow;
           amountUsedByRG += amountForRow;
           NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation");
           //rowFrame->DidResize();
           nsTableFrame::RePositionViews(rowFrame);
 
-          nsTableFrame::InvalidateTableFrame(rowFrame, rowRect, rowVisualOverflow,
-                                             false);
+          nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect,
+                                             rowVisualOverflow, false);
         }
         else {
-          if (amountUsed > 0 && yOriginRow != rowRect.y) {
+          if (amountUsed > 0 && yOriginRow != rowNormalRect.y) {
             rowFrame->InvalidateFrameSubtree();
-            rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow));
+            rowFrame->MovePositionBy(nsPoint(0, yOriginRow - rowNormalRect.y));
             nsTableFrame::RePositionViews(rowFrame);
             rowFrame->InvalidateFrameSubtree();
           }
-          yOriginRow += rowRect.height + cellSpacingY;
-          yEndRG += rowRect.height + cellSpacingY;
+          yOriginRow += rowNormalRect.height + cellSpacingY;
+          yEndRG += rowNormalRect.height + cellSpacingY;
         }
         rowFrame = rowFrame->GetNextRow();
       }
       if (amountUsed > 0) {
-        if (rgRect.y != yOriginRG) {
+        if (rgNormalRect.y != yOriginRG) {
           rgFrame->InvalidateFrameSubtree();
         }
 
-        rgFrame->SetRect(nsRect(rgRect.x, yOriginRG, rgRect.width,
-                                rgRect.height + amountUsedByRG));
-
-        nsTableFrame::InvalidateTableFrame(rgFrame, rgRect, rgVisualOverflow,
-                                           false);
+        nsRect origRgNormalRect = rgFrame->GetRect();
+        rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y));
+        rgFrame->SetSize(nsSize(rgNormalRect.width,
+                                rgNormalRect.height + amountUsedByRG));
+
+        nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
+                                           rgVisualOverflow, false);
       }
       // Make sure child views are properly positioned
     }
-    else if (amountUsed > 0 && yOriginRG != rgRect.y) {
+    else if (amountUsed > 0 && yOriginRG != rgNormalRect.y) {
       rgFrame->InvalidateFrameSubtree();
-      rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG));
+      rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y));
       // Make sure child views are properly positioned
       nsTableFrame::RePositionViews(rgFrame);
       rgFrame->InvalidateFrameSubtree();
     }
     yOriginRG = yEndRG;
   }
 
   ResizeCells(*this);
@@ -3606,18 +3615,25 @@ nsTableFrame::GetLogicalBaseline(Writing
   OrderRowGroups(orderedRowGroups);
   nsTableRowFrame* firstRow = nullptr;
   // XXX not sure if this should be the width of the containing block instead.
   nscoord containerWidth = mRect.width;
   for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
     nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
     if (rgFrame->GetRowCount()) {
       firstRow = rgFrame->GetFirstRow();
-      ascent = rgFrame->BStart(aWritingMode, containerWidth) +
-               firstRow->BStart(aWritingMode, containerWidth) +
+
+      nscoord rgNormalBStart =
+        LogicalRect(aWritingMode, rgFrame->GetNormalRect(), containerWidth)
+        .Origin(aWritingMode).B(aWritingMode);
+      nscoord firstRowNormalBStart =
+        LogicalRect(aWritingMode, firstRow->GetNormalRect(), containerWidth)
+        .Origin(aWritingMode).B(aWritingMode);
+
+      ascent = rgNormalBStart + firstRowNormalBStart +
                firstRow->GetRowBaseline(aWritingMode);
       break;
     }
   }
   if (!firstRow)
     ascent = BSize(aWritingMode);
   return ascent;
 }
--- a/layout/tables/nsTableFrame.h
+++ b/layout/tables/nsTableFrame.h
@@ -674,16 +674,17 @@ protected:
 
   // The following is a helper for CalcDesiredHeight 
  
   void DistributeHeightToRows(const nsHTMLReflowState& aReflowState,
                               nscoord                  aAmount);
 
   void PlaceChild(nsTableReflowState&  aReflowState,
                   nsIFrame*            aKidFrame,
+                  nsPoint              aKidPosition,
                   nsHTMLReflowMetrics& aKidDesiredSize,
                   const nsRect&        aOriginalKidRect,
                   const nsRect&        aOriginalKidVisualOverflow);
    void PlaceRepeatedFooter(nsTableReflowState& aReflowState,
                             nsTableRowGroupFrame *aTfoot,
                             nscoord aFooterHeight);
 
   nsIFrame* GetFirstBodyRowGroupFrame();
--- a/layout/tables/nsTablePainter.cpp
+++ b/layout/tables/nsTablePainter.cpp
@@ -87,21 +87,17 @@
    descendants' data) are not cached. The full loop is still executed, however,
    so that underlying layers can get painted at the cell level.
 
    The TableBackgroundPainter is then destroyed.
 
    Elements with stacking contexts set up their own painter to finish the
    painting process, since they were skipped. They call the appropriate
    sub-part of the loop (e.g. PaintRow) which will paint the frame and
-   descendants. Note that it is permissible according to CSS2.1 to ignore'
-   'position:relative' (and implicitly, 'opacity') on table parts so that
-   table parts can never create stacking contexts; if we want to, we can
-   implement that, and then we won't have to deal with TableBackgroundPainter
-   being used anywhere but from the nsTableFrame.
+   descendants.
    
    XXX views are going 
  */
 
 TableBackgroundPainter::TableBackgroundData::TableBackgroundData()
   : mFrame(nullptr),
     mVisible(false),
     mBorder(nullptr),
@@ -417,17 +413,25 @@ TableBackgroundPainter::PaintTable(nsTab
   }
 
   for (uint32_t i = 0; i < rowGroups.Length(); i++) {
     nsTableRowGroupFrame* rg = rowGroups[i];
     mRowGroup.SetFrame(rg);
     // Need to compute the right rect via GetOffsetTo, since the row
     // group may not be a child of the table.
     mRowGroup.mRect.MoveTo(rg->GetOffsetTo(aTableFrame));
-    if (mRowGroup.mRect.Intersects(mDirtyRect - mRenderPt)) {
+
+    // We have to draw backgrounds not only within the overflow region of this
+    // row group, but also possibly (in the case of column / column group
+    // backgrounds) at its pre-relative-positioning location.
+    nsRect rgVisualOverflow = rg->GetVisualOverflowRectRelativeToSelf();
+    nsRect rgOverflowRect = rgVisualOverflow + rg->GetPosition();
+    nsRect rgNormalRect = rgVisualOverflow + rg->GetNormalPosition();
+
+    if (rgOverflowRect.Union(rgNormalRect).Intersects(mDirtyRect - mRenderPt)) {
       nsresult rv = PaintRowGroup(rg, rg->IsPseudoStackingContextFromStyle());
       if (NS_FAILED(rv)) return rv;
     }
   }
   return NS_OK;
 }
 
 nsresult
@@ -465,26 +469,22 @@ TableBackgroundPainter::PaintRowGroup(ns
   /* translate everything into row group coord system*/
   if (eOrigin_TableRowGroup != mOrigin) {
     TranslateContext(mRowGroup.mRect.x, mRowGroup.mRect.y);
   }
   nsRect rgRect = mRowGroup.mRect;
   mRowGroup.mRect.MoveTo(0, 0);
 
   /* Find the right row to start with */
-  nscoord ignored; // We don't care about overflow above, since what we really
-                   // care about are backgrounds and overflow above doesn't
-                   // correspond to backgrounds, since cells can't span up from
-                   // their originating row.  We do care about overflow below,
-                   // however, since that can be due to rowspans.
 
   // Note that mDirtyRect  - mRenderPt is guaranteed to be in the row
   // group's coordinate system here, so passing its .y to
   // GetFirstRowContaining is ok.
-  nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &ignored);
+  nscoord overflowAbove;
+  nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &overflowAbove);
 
   // Sadly, it seems like there may be non-row frames in there... or something?
   // There are certainly null-checks in GetFirstRow() and GetNextRow().  :(
   while (cursor && cursor->GetType() != nsGkAtoms::tableRowFrame) {
     cursor = cursor->GetNextSibling();
   }
 
   // It's OK if cursor is null here.
@@ -495,19 +495,23 @@ TableBackgroundPainter::PaintRowGroup(ns
     // list for the rowgroup, so not having a cursor means that there's some
     // good reason we don't have a cursor and we shouldn't create one here.
     row = firstRow;
   }
   
   /* Finally paint */
   for (; row; row = row->GetNextRow()) {
     mRow.SetFrame(row);
-    if (mDirtyRect.YMost() - mRenderPt.y < mRow.mRect.y) { // Intersect wouldn't handle
-                                             // rowspans.
+    // Be sure to consider our positions both pre- and post-relative
+    // positioning, since we potentially need to paint at both places.
+    nscoord rowY = std::min(mRow.mRect.y, row->GetNormalPosition().y);
 
+    // Intersect wouldn't handle rowspans.
+    if (cursor &&
+        (mDirtyRect.YMost() - mRenderPt.y) <= (rowY - overflowAbove)) {
       // All done; cells originating in later rows can't intersect mDirtyRect.
       break;
     }
     
     nsresult rv = PaintRow(row, aPassThrough || row->IsPseudoStackingContextFromStyle());
     if (NS_FAILED(rv)) return rv;
   }
 
@@ -559,32 +563,46 @@ TableBackgroundPainter::PaintRow(nsTable
   /* Translate */
   if (eOrigin_TableRow == mOrigin) {
     /* If we originate from the row, then make the row the origin. */
     mRow.mRect.MoveTo(0, 0);
   }
   //else: Use row group's coord system -> no translation necessary
 
   for (nsTableCellFrame* cell = aFrame->GetFirstCell(); cell; cell = cell->GetNextCell()) {
-    //Translate to use the same coord system as mRow.
-    mCellRect = cell->GetRect() + mRow.mRect.TopLeft() + mRenderPt;
-    if (mCellRect.Intersects(mDirtyRect)) {
-      nsresult rv = PaintCell(cell, aPassThrough || cell->IsPseudoStackingContextFromStyle());
+    nsRect cellBGRect, rowBGRect, rowGroupBGRect, colBGRect;
+    ComputeCellBackgrounds(cell, cellBGRect, rowBGRect,
+                           rowGroupBGRect, colBGRect);
+
+    // Find the union of all the cell background layers.
+    nsRect combinedRect(cellBGRect);
+    combinedRect.UnionRect(combinedRect, rowBGRect);
+    combinedRect.UnionRect(combinedRect, rowGroupBGRect);
+    combinedRect.UnionRect(combinedRect, colBGRect);
+
+    if (combinedRect.Intersects(mDirtyRect)) {
+      bool passCell = aPassThrough || cell->IsPseudoStackingContextFromStyle();
+      nsresult rv = PaintCell(cell, cellBGRect, rowBGRect, rowGroupBGRect,
+                              colBGRect, passCell);
       if (NS_FAILED(rv)) return rv;
     }
   }
 
   /* Unload row data */
   mRow.Clear();
   return NS_OK;
 }
 
 nsresult
 TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell,
-                                  bool aPassSelf)
+                                  nsRect&           aCellBGRect,
+                                  nsRect&           aRowBGRect,
+                                  nsRect&           aRowGroupBGRect,
+                                  nsRect&           aColBGRect,
+                                  bool              aPassSelf)
 {
   NS_PRECONDITION(aCell, "null frame");
 
   const nsStyleTableBorder* cellTableStyle;
   cellTableStyle = aCell->StyleTableBorder();
   if (NS_STYLE_TABLE_EMPTY_CELLS_SHOW != cellTableStyle->mEmptyCells &&
       aCell->GetContentEmpty() && !mIsBorderCollapse) {
     return NS_OK;
@@ -598,49 +616,104 @@ TableBackgroundPainter::PaintCell(nsTabl
 
   //Paint column group background
   if (mCols && mCols[colIndex].mColGroup && mCols[colIndex].mColGroup->IsVisible()) {
     nsCSSRendering::PaintBackgroundWithSC(mPresContext, mRenderingContext,
                                           mCols[colIndex].mColGroup->mFrame, mDirtyRect,
                                           mCols[colIndex].mColGroup->mRect + mRenderPt,
                                           mCols[colIndex].mColGroup->mFrame->StyleContext(),
                                           *mCols[colIndex].mColGroup->mBorder,
-                                          mBGPaintFlags, &mCellRect);
+                                          mBGPaintFlags, &aColBGRect);
   }
 
   //Paint column background
   if (mCols && mCols[colIndex].mCol.IsVisible()) {
     nsCSSRendering::PaintBackgroundWithSC(mPresContext, mRenderingContext,
                                           mCols[colIndex].mCol.mFrame, mDirtyRect,
                                           mCols[colIndex].mCol.mRect + mRenderPt,
                                           mCols[colIndex].mCol.mFrame->StyleContext(),
                                           *mCols[colIndex].mCol.mBorder,
-                                          mBGPaintFlags, &mCellRect);
+                                          mBGPaintFlags, &aColBGRect);
   }
 
   //Paint row group background
   if (mRowGroup.IsVisible()) {
     nsCSSRendering::PaintBackgroundWithSC(mPresContext, mRenderingContext,
                                           mRowGroup.mFrame, mDirtyRect,
                                           mRowGroup.mRect + mRenderPt,
                                           mRowGroup.mFrame->StyleContext(),
                                           *mRowGroup.mBorder,
-                                          mBGPaintFlags, &mCellRect);
+                                          mBGPaintFlags, &aRowGroupBGRect);
   }
 
   //Paint row background
   if (mRow.IsVisible()) {
     nsCSSRendering::PaintBackgroundWithSC(mPresContext, mRenderingContext,
                                           mRow.mFrame, mDirtyRect,
                                           mRow.mRect + mRenderPt,
                                           mRow.mFrame->StyleContext(),
                                           *mRow.mBorder,
-                                          mBGPaintFlags, &mCellRect);
+                                          mBGPaintFlags, &aRowBGRect);
   }
 
   //Paint cell background in border-collapse unless we're just passing
   if (mIsBorderCollapse && !aPassSelf) {
     aCell->PaintCellBackground(mRenderingContext, mDirtyRect,
-                               mCellRect.TopLeft(), mBGPaintFlags);
+                               aCellBGRect.TopLeft(), mBGPaintFlags);
   }
 
   return NS_OK;
 }
+
+void
+TableBackgroundPainter::ComputeCellBackgrounds(nsTableCellFrame* aCell,
+                                               nsRect&           aCellBGRect,
+                                               nsRect&           aRowBGRect,
+                                               nsRect&           aRowGroupBGRect,
+                                               nsRect&           aColBGRect)
+{
+  // We need to compute table background layer rects for this cell space,
+  // adjusted for possible relative positioning. This behavior is not specified
+  // at the time of this writing, but the approach below should be web
+  // compatible.
+  //
+  // Our goal is that relative positioning of a table part should leave
+  // backgrounds *under* that part unchanged. ("Under" being defined by CSS 2.1
+  // Section 17.5.1.) If a cell is positioned, we do not expect the row
+  // background to move. On the other hand, the backgrounds of layers *above*
+  // the positioned part are taken along for the ride -- for example,
+  // positioning a row group will also cause the row background to be drawn in
+  // the new location, unless it has further positioning applied.
+  //
+  // Each table part layer has its position stored in the coordinate space of
+  // the layer below (which is to say, its geometric parent), and the stored
+  // position is the post-relative-positioning one.  The position of each
+  // background layer rect is thus determined by peeling off successive table
+  // part layers, removing the contribution of each layer's positioning one by
+  // one.  Every rect we generate will be the same size, the size of the cell
+  // space.
+
+  // We cannot rely on the row group background data to be available, since some
+  // callers enter through PaintRow.
+  nsIFrame* rowGroupFrame =
+    mRowGroup.mFrame ? mRowGroup.mFrame : mRow.mFrame->GetParent();
+
+  // The cell background goes at the cell's position, translated to use the same
+  // coordinate system as mRow.
+  aCellBGRect = aCell->GetRect() + mRow.mRect.TopLeft() + mRenderPt;
+
+  // The row background goes at the normal position of the cell, which is to say
+  // the position without relative positioning applied.
+  aRowBGRect = aCellBGRect + (aCell->GetNormalPosition() - aCell->GetPosition());
+
+  // The row group background goes at the position we'd find the cell if neither
+  // the cell's relative positioning nor the row's were applied.
+  aRowGroupBGRect = aRowBGRect +
+                    (mRow.mFrame->GetNormalPosition() - mRow.mFrame->GetPosition());
+
+  // The column and column group backgrounds (they're always at the same
+  // location, since relative positioning doesn't apply to columns or column
+  // groups) are drawn at the position we'd find the cell if none of the cell's,
+  // row's, or row group's relative positioning were applied.
+  aColBGRect = aRowGroupBGRect +
+             (rowGroupFrame->GetNormalPosition() - rowGroupFrame->GetPosition());
+
+}
--- a/layout/tables/nsTablePainter.h
+++ b/layout/tables/nsTablePainter.h
@@ -126,22 +126,44 @@ class TableBackgroundPainter
      */
     nsresult PaintRowGroup(nsTableRowGroupFrame* aFrame,
                            bool                  aPassThrough);
     nsresult PaintRow(nsTableRowFrame* aFrame,
                       bool             aPassThrough);
 
     /** Paint table background layers for this cell space
       * Also paints cell's own background in border-collapse mode
-      * @param aFrame      - the cell
-      * @param aPassSelf   - pass this cell; i.e. paint only underlying layers
+      * @param aCell           - the cell
+      * @param aCellBGRect     - background rect for the cell
+      * @param aRowBGRect      - background rect for the row
+      * @param aRowGroupBGRect - background rect for the row group
+      * @param aColBGRect      - background rect for the column and column group
+      * @param aPassSelf       - pass this cell; i.e. paint only underlying layers
       */
-    nsresult PaintCell(nsTableCellFrame* aFrame,
+    nsresult PaintCell(nsTableCellFrame* aCell,
+                       nsRect&           aCellBGRect,
+                       nsRect&           aRowBGRect,
+                       nsRect&           aRowGroupBGRect,
+                       nsRect&           aColBGRect,
                        bool              aPassSelf);
 
+    /** Compute table background layer positions for this cell space
+      * @param aCell              - the cell
+      * @param aCellBGRectOut     - outparam: background rect for the cell
+      * @param aRowBGRectOut      - outparam: background rect for the row
+      * @param aRowGroupBGRectOut - outparam: background rect for the row group
+      * @param aColBGRectOut      - outparam: background rect for the column
+                                    and column group
+      */
+    void ComputeCellBackgrounds(nsTableCellFrame* aCell,
+                                nsRect&           aCellBGRect,
+                                nsRect&           aRowBGRect,
+                                nsRect&           aRowGroupBGRect,
+                                nsRect&           aColBGRect);
+
     /** Translate mRenderingContext, mDirtyRect, and mCols' column and
       * colgroup coords
       * @param aDX - origin's x-coord change
       * @param aDY - origin's y-coord change
       */
     void TranslateContext(nscoord aDX,
                           nscoord aDY);
 
@@ -209,15 +231,14 @@ class TableBackgroundPainter
 #endif
     bool                 mIsBorderCollapse;
     Origin               mOrigin; //user's table frame type
 
     ColData*             mCols;  //array of columns' ColData
     uint32_t             mNumCols;
     TableBackgroundData  mRowGroup; //current row group
     TableBackgroundData  mRow;      //current row
-    nsRect               mCellRect; //current cell's rect
 
     nsStyleBorder        mZeroBorder;  //cached zero-width border
     uint32_t             mBGPaintFlags;
 };
 
 #endif
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -1,12 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
+
+#include "mozilla/Maybe.h"
+
 #include "nsTableRowFrame.h"
 #include "nsTableRowGroupFrame.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsStyleConsts.h"
 #include "nsGkAtoms.h"
 #include "nsIContent.h"
@@ -390,17 +393,17 @@ nscoord nsTableRowFrame::GetRowBaseline(
   // bbbbbbbbbbbbbbbbbb
 
   nsTableIterator iter(*this);
   nsIFrame* childFrame = iter.First();
   nscoord ascent = 0;
    while (childFrame) {
     if (IS_TABLE_CELL(childFrame->GetType())) {
       nsIFrame* firstKid = childFrame->GetFirstPrincipalChild();
-      ascent = std::max(ascent, firstKid->GetRect().YMost());
+      ascent = std::max(ascent, firstKid->GetNormalRect().YMost());
     }
     // Get the next child
     childFrame = iter.Next();
   }
   return ascent;
 }
 nscoord
 nsTableRowFrame::GetHeight(nscoord aPctBasis) const
@@ -856,25 +859,29 @@ nsTableRowFrame::ReflowChildren(nsPresCo
                            iter.IsLeftToRight(), false);
     }
 
     // remember the rightmost (ltr) or leftmost (rtl) column this cell spans into
     prevColIndex = (iter.IsLeftToRight()) ? cellColIndex + (cellColSpan - 1) : cellColIndex;
 
     // Reflow the child frame
     nsRect kidRect = kidFrame->GetRect();
+    nsPoint origKidNormalPosition = kidFrame->GetNormalPosition();
+    MOZ_ASSERT(origKidNormalPosition.y == 0);
     nsRect kidVisualOverflow = kidFrame->GetVisualOverflowRect();
+    nsPoint kidPosition(x, 0);
     bool firstReflow =
       (kidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0;
 
     if (doReflowChild) {
       // Calculate the available width for the table cell using the known column widths
       nscoord availCellWidth =
         CalcAvailWidth(aTableFrame, *cellFrame);
 
+      Maybe<nsTableCellReflowState> kidReflowState;
       nsHTMLReflowMetrics desiredSize(aReflowState);
 
       // If the avail width is not the same as last time we reflowed the cell or
       // the cell wants to be bigger than what was available last time or
       // it is a style change reflow or we are printing, then we must reflow the
       // cell. Otherwise we can skip the reflow.
       // XXXldb Why is this condition distinct from doReflowChild above?
       WritingMode rowWM = aReflowState.GetWritingMode();
@@ -885,39 +892,39 @@ nsTableRowFrame::ReflowChildren(nsPresCo
           (GetStateBits() & NS_FRAME_IS_DIRTY)                      ||
           isPaginated                                               ||
           NS_SUBTREE_DIRTY(cellFrame)                               ||
           // See if it needs a special reflow, or if it had one that we need to undo.
           (cellFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) ||
           HasPctHeight()) {
         // Reflow the cell to fit the available width, height
         // XXX The old IR_ChildIsDirty code used availCellWidth here.
-        nsSize  kidAvailSize(availCellWidth, aReflowState.AvailableHeight());
+        nsSize kidAvailSize(availCellWidth, aReflowState.AvailableHeight());
 
         // Reflow the child
-        nsTableCellReflowState
-          kidReflowState(aPresContext, aReflowState, kidFrame,
-                         LogicalSize(kidFrame->GetWritingMode(),
-                                     kidAvailSize),
-                         nsHTMLReflowState::CALLER_WILL_INIT);
+        kidReflowState.emplace(aPresContext, aReflowState, kidFrame,
+                               LogicalSize(kidFrame->GetWritingMode(),
+                                           kidAvailSize),
+                               // Cast needed for gcc 4.4.
+                               uint32_t(nsHTMLReflowState::CALLER_WILL_INIT));
         InitChildReflowState(*aPresContext, kidAvailSize, borderCollapse,
-                             kidReflowState);
+                             *kidReflowState);
 
         nsReflowStatus status;
-        ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState,
+        ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowState,
                     x, 0, 0, status);
 
         // allow the table to determine if/how the table needs to be rebalanced
         // If any of the cells are not complete, then we're not complete
         if (NS_FRAME_IS_NOT_COMPLETE(status)) {
           aStatus = NS_FRAME_NOT_COMPLETE;
         }
       }
       else {
-        if (x != kidRect.x) {
+        if (x != origKidNormalPosition.x) {
           kidFrame->InvalidateFrameSubtree();
         }
         
         desiredSize.SetSize(cellWM, cellDesiredSize);
         desiredSize.mOverflowAreas = cellFrame->GetOverflowAreas();
 
         // if we are in a floated table, our position is not yet established, so we cannot reposition our views
         // the containing block will do this for us after positioning the table
@@ -951,29 +958,41 @@ nsTableRowFrame::ReflowChildren(nsPresCo
         if (1 == rowSpan) {
           SetContentHeight(cellMaxHeight);
         }
       }
 
       // Place the child
       desiredSize.ISize(rowWM) = availCellWidth;
 
-      FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr, x, 0, 0);
+      if (kidReflowState) {
+        // We reflowed. Apply relative positioning in the normal way.
+        kidReflowState->ApplyRelativePositioning(&kidPosition);
+      } else {
+        // We didn't reflow. To take relative positioning into account,
+        // translate the new position by the vector from the previous 'normal'
+        // position to the previous position.
+        // XXX(seth): This doesn't work for 'position: sticky'.
+        kidPosition += kidRect.TopLeft() - origKidNormalPosition;
+      }
+      FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr,
+                        kidPosition.x, kidPosition.y, 0);
 
       nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidVisualOverflow,
                                          firstReflow);
       
       x += desiredSize.Width();  
     }
     else {
-      if (kidRect.x != x) {
+      if (x != origKidNormalPosition.x) {
         // Invalidate the old position
         kidFrame->InvalidateFrameSubtree();
-        // move to the new position
-        kidFrame->SetPosition(nsPoint(x, kidRect.y));
+        // Move to the new position. As above, we need to account for relative
+        // positioning.
+        kidFrame->MovePositionBy(nsPoint(x - origKidNormalPosition.x, 0));
         nsTableFrame::RePositionViews(kidFrame);
         // invalidate the new position
         kidFrame->InvalidateFrameSubtree();
       }
       // we need to account for the cell's width even if it isn't reflowed
       x += kidRect.width;
 
       if (kidFrame->GetNextInFlow()) {
@@ -1258,24 +1277,26 @@ nsTableRowFrame::CollapseRowIfNecessary(
             nsRect nextRect = rowFrame->GetRect();
             cRect.height += nextRect.height +
                             tableFrame->GetCellSpacingY(rowFrame->GetRowIndex());
           }
           rowFrame = rowFrame->GetNextRow();
         }
 
         nsRect oldCellRect = cellFrame->GetRect();
+        nsPoint oldCellNormalPos = cellFrame->GetNormalPosition();
         nsRect oldCellVisualOverflow = cellFrame->GetVisualOverflowRect();
 
-        if (aRowOffset == 0 && cRect.TopLeft() != oldCellRect.TopLeft()) {
+        if (aRowOffset == 0 && cRect.TopLeft() != oldCellNormalPos) {
           // We're moving the cell.  Invalidate the old overflow area
           cellFrame->InvalidateFrameSubtree();
         }
         
-        cellFrame->SetRect(cRect);
+        cellFrame->MovePositionBy(cRect.TopLeft() - oldCellNormalPos);
+        cellFrame->SetSize(cRect.Size());
 
         // XXXbz This looks completely bogus in the cases when we didn't
         // collapse the cell!
         nsRect cellBounds(0, 0, cRect.width, cRect.height);
         nsOverflowAreas cellOverflow(cellBounds, cellBounds);
         cellFrame->FinishAndStoreOverflow(cellOverflow, cRect.Size());
         nsTableFrame::RePositionViews(cellFrame);
         ConsiderChildOverflow(overflow, cellFrame);
--- a/layout/tables/nsTableRowGroupFrame.cpp
+++ b/layout/tables/nsTableRowGroupFrame.cpp
@@ -200,17 +200,18 @@ DisplayRows(nsDisplayListBuilder* aBuild
   // approximate it by checking it for |f|: if it's true for any row
   // in |f| then it's true for |f| itself.
   nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(f) ?
     nullptr : f->GetFirstRowContaining(aDirtyRect.y, &overflowAbove);
   
   if (kid) {
     // have a cursor, use it
     while (kid) {
-      if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost())
+      if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost() &&
+          kid->GetNormalRect().y - overflowAbove >= aDirtyRect.YMost())
         break;
       f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
       kid = kid->GetNextSibling();
     }
     return;
   }
   
   // No cursor. Traverse children the hard way and build a cursor while we're at it
@@ -272,26 +273,27 @@ nsTableRowGroupFrame::GetLogicalSkipSide
 }
 
 // Position and size aKidFrame and update our reflow state. The origin of
 // aKidRect is relative to the upper-left origin of our frame
 void 
 nsTableRowGroupFrame::PlaceChild(nsPresContext*         aPresContext,
                                  nsRowGroupReflowState& aReflowState,
                                  nsIFrame*              aKidFrame,
+                                 nsPoint                aKidPosition,
                                  nsHTMLReflowMetrics&   aDesiredSize,
                                  const nsRect&          aOriginalKidRect,
                                  const nsRect&          aOriginalKidVisualOverflow)
 {
   bool isFirstReflow =
     (aKidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0;
 
   // Place and size the child
-  FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr, 0,
-                    aReflowState.y, 0);
+  FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr,
+                    aKidPosition.x, aKidPosition.y, 0);
 
   nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect,
                                      aOriginalKidVisualOverflow, isFirstReflow);
 
   // Adjust the running y-offset
   aReflowState.y += aDesiredSize.Height();
 
   // If our height is constrained then update the available height
@@ -395,41 +397,41 @@ nsTableRowGroupFrame::ReflowChildren(nsP
       if (aReflowState.reflowState.IsHResize()) {
         kidReflowState.SetHResize(true);
       }
      
       NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame, 
                    "If we're not on the first frame, we should have a "
                    "previous sibling...");
       // If prev row has nonzero YMost, then we can't be at the top of the page
-      if (prevKidFrame && prevKidFrame->GetRect().YMost() > 0) {
+      if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) {
         kidReflowState.mFlags.mIsTopOfPage = false;
       }
 
       ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState,
                   0, aReflowState.y, 0, aStatus);
+      nsPoint kidPosition(0, aReflowState.y);
+      kidReflowState.ApplyRelativePositioning(&kidPosition);
 
       // Place the child
-      PlaceChild(aPresContext, aReflowState, kidFrame, desiredSize,
-                 oldKidRect, oldKidVisualOverflow);
+      PlaceChild(aPresContext, aReflowState, kidFrame, kidPosition,
+                 desiredSize, oldKidRect, oldKidVisualOverflow);
       aReflowState.y += cellSpacingY;
 
       if (!reflowAllKids) {
         if (IsSimpleRowFrame(aReflowState.tableFrame, kidFrame)) {
           // Inform the row of its new height.
           rowFrame->DidResize();
           // the overflow area may have changed inflate the overflow area
           const nsStylePosition *stylePos = StylePosition();
           nsStyleUnit unit = stylePos->mHeight.GetUnit();
           if (aReflowState.tableFrame->IsAutoHeight() &&
               unit != eStyleUnit_Coord) {
             // Because other cells in the row may need to be aligned
             // differently, repaint the entire row
-            nsRect kidRect(0, aReflowState.y,
-                           desiredSize.Width(), desiredSize.Height());
             InvalidateFrame();
           }
           else if (oldKidRect.height != desiredSize.Height())
             needToCalcRowHeights = true;
         } else {
           needToCalcRowHeights = true;
         }
       }
@@ -544,17 +546,17 @@ nsTableRowGroupFrame::CalculateRowHeight
 
   int32_t startRowIndex = GetStartRowIndex();
   // find the row corresponding to the row index we just found
   nsTableRowFrame* startRowFrame = GetFirstRow();
 
   if (!startRowFrame) return;
 
   // the current row group height is the y origin of the 1st row we are about to calculated a height for
-  nscoord startRowGroupHeight = startRowFrame->GetPosition().y;
+  nscoord startRowGroupHeight = startRowFrame->GetNormalPosition().y;
 
   int32_t numRows = GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex());
   // collect the current height of each row.  nscoord* rowHeights = nullptr;
   if (numRows <= 0)
     return;
 
   nsTArray<RowInfo> rowInfo;
   if (!rowInfo.AppendElements(numRows)) {
@@ -773,35 +775,36 @@ nsTableRowGroupFrame::CalculateRowHeight
     rowGroupHeight = aReflowState.ComputedHeight();
   }
 
   nscoord yOrigin = startRowGroupHeight;
   // update the rows with their (potentially) new heights
   for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) {
     nsRect rowBounds = rowFrame->GetRect();
     nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect();
+    nscoord deltaY = yOrigin - rowFrame->GetNormalPosition().y;
 
-    bool movedFrame = (rowBounds.y != yOrigin);  
     nscoord rowHeight = (rowInfo[rowIndex].height > 0) ? rowInfo[rowIndex].height : 0;
     
-    if (movedFrame || (rowHeight != rowBounds.height)) {
+    if (deltaY != 0 || (rowHeight != rowBounds.height)) {
       // Resize/move the row to its final size and position
-      if (movedFrame) {
+      if (deltaY != 0) {
         rowFrame->InvalidateFrameSubtree();
       }
       
-      rowFrame->SetRect(nsRect(rowBounds.x, yOrigin, rowBounds.width,
-                               rowHeight));
+      rowFrame->MovePositionBy(nsPoint(0, deltaY));
+      rowFrame->SetSize(nsSize(rowBounds.width, rowHeight));
 
       nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowVisualOverflow,
                                          false);
-    }
-    if (movedFrame) {
-      nsTableFrame::RePositionViews(rowFrame);
-      // XXXbz we don't need to update our overflow area?
+
+      if (deltaY != 0) {
+        nsTableFrame::RePositionViews(rowFrame);
+        // XXXbz we don't need to update our overflow area?
+      }
     }
     yOrigin += rowHeight + tableFrame->GetCellSpacingY(startRowIndex + rowIndex);
   }
 
   if (isPaginated && styleHeightAllocation) {
     // since the row group has a style height, cache the row heights, so next in flows can honor them 
     CacheRowHeightsForPrinting(aPresContext, GetFirstRow());
   }
@@ -864,21 +867,22 @@ nsTableRowGroupFrame::CollapseRowGroupIf
 }
 
 // Move a child that was skipped during a reflow.
 void
 nsTableRowGroupFrame::SlideChild(nsRowGroupReflowState& aReflowState,
                                  nsIFrame*              aKidFrame)
 {
   // Move the frame if we need to
-  nsPoint oldPosition = aKidFrame->GetPosition();
+  nsPoint oldPosition = aKidFrame->GetNormalPosition();
   nsPoint newPosition = oldPosition;
   newPosition.y = aReflowState.y;
   if (oldPosition.y != newPosition.y) {
     aKidFrame->InvalidateFrameSubtree();
+    aReflowState.reflowState.ApplyRelativePositioning(&newPosition);
     aKidFrame->SetPosition(newPosition);
     nsTableFrame::RePositionViews(aKidFrame);
     aKidFrame->InvalidateFrameSubtree();
   }
 }
 
 // Create a continuing frame, add it to the child list, and then push it
 // and the frames that follow
@@ -922,32 +926,32 @@ nsTableRowGroupFrame::SplitSpanningCells
   const bool borderCollapse = aTable.IsBorderCollapse();
   int32_t lastRowIndex = aLastRow.GetRowIndex();
   bool wasLast = false;
   bool haveRowSpan = false;
   // Iterate the rows between aFirstRow and aLastRow
   for (nsTableRowFrame* row = &aFirstRow; !wasLast; row = row->GetNextRow()) {
     wasLast = (row == &aLastRow);
     int32_t rowIndex = row->GetRowIndex();
-    nsPoint rowPos = row->GetPosition();
+    nsPoint rowPos = row->GetNormalPosition();
     // Iterate the cells looking for those that have rowspan > 1
     for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) {
       int32_t rowSpan = aTable.GetEffectiveRowSpan(rowIndex, *cell);
       // Only reflow rowspan > 1 cells which span aLastRow. Those which don't span aLastRow
       // were reflowed correctly during the unconstrained height reflow. 
       if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) {
         haveRowSpan = true;
         nsReflowStatus status;
         // Ask the row to reflow the cell to the height of all the rows it spans up through aLastRow
         // aAvailHeight is the space between the row group start and the end of the page
         nscoord cellAvailHeight = aSpanningRowBottom - rowPos.y;
         NS_ASSERTION(cellAvailHeight >= 0, "No space for cell?");
         bool isTopOfPage = (row == &aFirstRow) && aFirstRowIsTopOfPage;
 
-        nsRect rowRect = row->GetRect();
+        nsRect rowRect = row->GetNormalRect();
         nsSize rowAvailSize(aReflowState.AvailableWidth(),
                             std::max(aReflowState.AvailableHeight() - rowRect.y,
                                    0));
         // don't let the available height exceed what
         // CalculateRowHeights set for it
         rowAvailSize.height = std::min(rowAvailSize.height, rowRect.height);
         nsHTMLReflowState rowReflowState(&aPresContext, aReflowState, row,
                                          LogicalSize(row->GetWritingMode(),
@@ -987,17 +991,17 @@ nsTableRowGroupFrame::SplitSpanningCells
               aContRow->InsertCellFrame(contCell, colIndex);
             }
           }
         }
       }
     }
   }
   if (!haveRowSpan) {
-    aDesiredHeight = aLastRow.GetRect().YMost();
+    aDesiredHeight = aLastRow.GetNormalRect().YMost();
   }
 }
 
 // Remove the next-in-flow of the row, its cells and their cell blocks. This 
 // is necessary in case the row doesn't need a continuation later on or needs 
 // a continuation which doesn't have the same number of cells that now exist. 
 void
 nsTableRowGroupFrame::UndoContinuedRow(nsPresContext*   aPresContext,
@@ -1068,17 +1072,17 @@ nsTableRowGroupFrame::SplitRowGroup(nsPr
   // reflowing its cell as an optimization.
   aTableFrame->SetGeometryDirty();
 
   // Walk each of the row frames looking for the first row frame that doesn't fit 
   // in the available space
   for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame; rowFrame = rowFrame->GetNextRow()) {
     bool rowIsOnPage = true;
     nscoord cellSpacingY = aTableFrame->GetCellSpacingY(rowFrame->GetRowIndex());
-    nsRect rowRect = rowFrame->GetRect();
+    nsRect rowRect = rowFrame->GetNormalRect();
     // See if the row fits on this page
     if (rowRect.YMost() > availHeight) {
       nsTableRowFrame* contRow = nullptr;
       // Reflow the row in the availabe space and have it split if it is the 1st
       // row (on the page) or there is at least 5% of the current page available 
       // XXX this 5% should be made a preference 
       if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) { 
         nsSize availSize(availWidth, std::max(availHeight - rowRect.y, 0));
@@ -1174,17 +1178,17 @@ nsTableRowGroupFrame::SplitRowGroup(nsPr
       nscoord spanningRowBottom = availHeight;
       if (!rowIsOnPage) {
         NS_ASSERTION(!contRow, "We should not have created a continuation if none of this row fits");
         if (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowState)) {
           aStatus = NS_INLINE_LINE_BREAK_BEFORE();
           break;
         }
         if (prevRowFrame) {
-          spanningRowBottom = prevRowFrame->GetRect().YMost();
+          spanningRowBottom = prevRowFrame->GetNormalRect().YMost();
           lastRowThisPage = prevRowFrame;
           isTopOfPage = (lastRowThisPage == firstRowThisPage) && aReflowState.mFlags.mIsTopOfPage;
           aStatus = NS_FRAME_NOT_COMPLETE;
         }
         else {
           // We can't push children, so let our parent reflow us again with more space
           aDesiredSize.Height() = rowRect.YMost();
           aStatus = NS_FRAME_COMPLETE;
@@ -1211,17 +1215,17 @@ nsTableRowGroupFrame::SplitRowGroup(nsPr
             UndoContinuedRow(aPresContext, contRow);
             contRow = nullptr;
           }
         }
         else { // (firstTruncatedRow != firstRowThisPage)
           // Try to put firstTruncateRow on the next page 
           nsTableRowFrame* rowBefore = ::GetRowBefore(*firstRowThisPage, *firstTruncatedRow);
           nscoord oldSpanningRowBottom = spanningRowBottom;
-          spanningRowBottom = rowBefore->GetRect().YMost();
+          spanningRowBottom = rowBefore->GetNormalRect().YMost();
 
           UndoContinuedRow(aPresContext, contRow);
           contRow = nullptr;
           nsTableRowFrame* oldLastRowThisPage = lastRowThisPage;
           lastRowThisPage = rowBefore;
           aStatus = NS_FRAME_NOT_COMPLETE;
 
           // Call SplitSpanningCells again with rowBefore as the last row on the page
@@ -1890,35 +1894,46 @@ nsTableRowGroupFrame::GetFirstRowContain
   // we don't need to check that here.
   
   // We use property->mOverflowBelow here instead of computing the frame's
   // true overflowArea.YMost(), because it is essential for the thresholds
   // to form a monotonically increasing sequence. Otherwise we would break
   // encountering a row whose overflowArea.YMost() is <= aY but which has
   // a row above it containing cell(s) that span to include aY.
   while (cursorIndex > 0 &&
-         cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) {
+         cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow > aY) {
     --cursorIndex;
     cursorFrame = property->mFrames[cursorIndex];
   }
   while (cursorIndex + 1 < frameCount &&
-         cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) {
+         cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow <= aY) {
     ++cursorIndex;
     cursorFrame = property->mFrames[cursorIndex];
   }
 
   property->mCursorIndex = cursorIndex;
   *aOverflowAbove = property->mOverflowAbove;
   return cursorFrame;
 }
 
 bool
 nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame)
 {
-  nsRect overflowRect = aFrame->GetVisualOverflowRect();
+  // Relative positioning can cause table parts to move, but we will still paint
+  // the backgrounds for the parts under them at their 'normal' position. That
+  // means that we must consider the overflow rects at both positions. For
+  // example, if we use relative positioning to move a row-spanning cell, we
+  // will still paint the row background for that cell at its normal position,
+  // which will overflow the row.
+  // XXX(seth): This probably isn't correct in the presence of transforms.
+  nsRect positionedOverflowRect = aFrame->GetVisualOverflowRect();
+  nsPoint positionedToNormal = aFrame->GetNormalPosition() - aFrame->GetPosition();
+  nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal;
+
+  nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect);
   if (overflowRect.IsEmpty())
     return true;
   nscoord overflowAbove = -overflowRect.y;
   nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height;
   mOverflowAbove = std::max(mOverflowAbove, overflowAbove);
   mOverflowBelow = std::max(mOverflowBelow, overflowBelow);
   return mFrames.AppendElement(aFrame) != nullptr;
 }
--- a/layout/tables/nsTableRowGroupFrame.h
+++ b/layout/tables/nsTableRowGroupFrame.h
@@ -332,16 +332,17 @@ protected:
                             bool               aBorderCollapse,
                             nsHTMLReflowState& aReflowState);
   
   virtual LogicalSides GetLogicalSkipSides(const nsHTMLReflowState* aReflowState = nullptr) const MOZ_OVERRIDE;
 
   void PlaceChild(nsPresContext*         aPresContext,
                   nsRowGroupReflowState& aReflowState,
                   nsIFrame*              aKidFrame,
+                  nsPoint                aKidPosition,
                   nsHTMLReflowMetrics&   aDesiredSize,
                   const nsRect&          aOriginalKidRect,
                   const nsRect&          aOriginalKidVisualOverflow);
 
   void CalculateRowHeights(nsPresContext*           aPresContext, 
                            nsHTMLReflowMetrics&     aDesiredSize,
                            const nsHTMLReflowState& aReflowState);