Backed out changeset 3dc8c2aea9bf (bug 929484)
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 04 May 2017 16:59:59 +0200
changeset 572812 5d9cdb47c1b93d649ec3437a0c6d545d058ca672
parent 572811 e6a8f25229da6067a73f52a3cf762f8b19cf4880
child 572813 3f788935a3a4c1c6b65729298c497f06687f52ad
push id57195
push userbmo:rbarker@mozilla.com
push dateThu, 04 May 2017 20:08:56 +0000
bugs929484
milestone55.0a1
backs out3dc8c2aea9bf2e5b54a18b67feed9723696b42eb
Backed out changeset 3dc8c2aea9bf (bug 929484)
layout/painting/nsDisplayItemTypesList.h
layout/tables/moz.build
layout/tables/nsTableCellFrame.cpp
layout/tables/nsTableCellFrame.h
layout/tables/nsTableFrame.cpp
layout/tables/nsTableFrame.h
layout/tables/nsTablePainter.cpp
layout/tables/nsTablePainter.h
layout/tables/nsTableRowFrame.cpp
layout/tables/nsTableRowFrame.h
layout/tables/nsTableRowGroupFrame.cpp
layout/tables/nsTableRowGroupFrame.h
--- a/layout/painting/nsDisplayItemTypesList.h
+++ b/layout/painting/nsDisplayItemTypesList.h
@@ -51,16 +51,19 @@ DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_RE
 DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT)
 DECLARE_DISPLAY_ITEM_TYPE(MASK)
 DECLARE_DISPLAY_ITEM_TYPE(FILTER)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_OUTER_SVG)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_GROUP_BACKGROUND)
+DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_BACKGROUND)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE)
 DECLARE_DISPLAY_ITEM_TYPE(TEXT)
 DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW)
 DECLARE_DISPLAY_ITEM_TYPE_FLAGS(TRANSFORM,TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE_FLAGS(PERSPECTIVE,TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(VIDEO)
 DECLARE_DISPLAY_ITEM_TYPE(WRAP_LIST)
 DECLARE_DISPLAY_ITEM_TYPE(ZOOM)
--- a/layout/tables/moz.build
+++ b/layout/tables/moz.build
@@ -16,16 +16,17 @@ EXPORTS += [
 UNIFIED_SOURCES += [
     'BasicTableLayoutStrategy.cpp',
     'FixedTableLayoutStrategy.cpp',
     'nsCellMap.cpp',
     'nsTableCellFrame.cpp',
     'nsTableColFrame.cpp',
     'nsTableColGroupFrame.cpp',
     'nsTableFrame.cpp',
+    'nsTablePainter.cpp',
     'nsTableRowFrame.cpp',
     'nsTableRowGroupFrame.cpp',
     'nsTableWrapperFrame.cpp',
     'SpanningCellSorter.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -7,16 +7,17 @@
 
 #include "gfxUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "nsTableFrame.h"
 #include "nsTableColFrame.h"
 #include "nsTableRowFrame.h"
 #include "nsTableRowGroupFrame.h"
+#include "nsTablePainter.h"
 #include "nsStyleContext.h"
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsRenderingContext.h"
 #include "nsCSSRendering.h"
 #include "nsIContent.h"
 #include "nsGenericHTMLElement.h"
 #include "nsAttrValueInlines.h"
@@ -380,16 +381,29 @@ nsTableCellFrame::PaintBackground(nsRend
   nsRect rect(aPt, GetSize());
   nsCSSRendering::PaintBGParams params =
     nsCSSRendering::PaintBGParams::ForAllLayers(*PresContext(),
                                                 aDirtyRect, rect,
                                                 this, aFlags);
   return nsCSSRendering::PaintStyleImageLayer(params, aRenderingContext);
 }
 
+// Called by nsTablePainter
+DrawResult
+nsTableCellFrame::PaintCellBackground(nsRenderingContext& aRenderingContext,
+                                      const nsRect& aDirtyRect, nsPoint aPt,
+                                      uint32_t aFlags)
+{
+  if (!StyleVisibility()->IsVisible()) {
+    return DrawResult::SUCCESS;
+  }
+
+  return PaintBackground(aRenderingContext, aDirtyRect, aPt, aFlags);
+}
+
 nsresult
 nsTableCellFrame::ProcessBorders(nsTableFrame* aFrame,
                                  nsDisplayListBuilder* aBuilder,
                                  const nsDisplayListSet& aLists)
 {
   const nsStyleBorder* borderStyle = StyleBorder();
   if (aFrame->IsBorderCollapse() || !borderStyle->HasBorder())
     return NS_OK;
--- a/layout/tables/nsTableCellFrame.h
+++ b/layout/tables/nsTableCellFrame.h
@@ -107,16 +107,21 @@ public:
   virtual void NotifyPercentBSize(const ReflowInput& aReflowInput) override;
 
   virtual bool NeedsToObserve(const ReflowInput& aReflowInput) override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) override;
 
+  DrawResult PaintCellBackground(nsRenderingContext& aRenderingContext,
+                                 const nsRect& aDirtyRect, nsPoint aPt,
+                                 uint32_t aFlags);
+
+ 
   virtual nsresult ProcessBorders(nsTableFrame* aFrame,
                                   nsDisplayListBuilder* aBuilder,
                                   const nsDisplayListSet& aLists);
 
   virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
   virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
   virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override;
 
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -18,16 +18,17 @@
 #include "nsCellMap.h"
 #include "nsTableCellFrame.h"
 #include "nsHTMLParts.h"
 #include "nsTableColFrame.h"
 #include "nsTableColGroupFrame.h"
 #include "nsTableRowFrame.h"
 #include "nsTableRowGroupFrame.h"
 #include "nsTableWrapperFrame.h"
+#include "nsTablePainter.h"
 
 #include "BasicTableLayoutStrategy.h"
 #include "FixedTableLayoutStrategy.h"
 
 #include "nsPresContext.h"
 #include "nsContentUtils.h"
 #include "nsCSSRendering.h"
 #include "nsGkAtoms.h"
@@ -1272,16 +1273,46 @@ nsDisplayTableBorderCollapse::Paint(nsDi
   // But that would mean modifying PaintBCBorders, ugh
   AutoRestoreTransform autoRestoreTransform(drawTarget);
   drawTarget->SetTransform(
       drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
 
   static_cast<nsTableFrame*>(mFrame)->PaintBCBorders(*drawTarget, mVisibleRect - pt);
 }
 
+class nsDisplayTableBorderBackground : public nsDisplayTableItem {
+public:
+  nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder,
+                                 nsTableFrame* aFrame,
+                                 bool aDrawsBackground) :
+    nsDisplayTableItem(aBuilder, aFrame, aDrawsBackground) {
+    MOZ_COUNT_CTOR(nsDisplayTableBorderBackground);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayTableBorderBackground() {
+    MOZ_COUNT_DTOR(nsDisplayTableBorderBackground);
+  }
+#endif
+
+  virtual void Paint(nsDisplayListBuilder* aBuilder,
+                     nsRenderingContext* aCtx) override;
+  NS_DISPLAY_DECL_NAME("TableBorderBackground", TYPE_TABLE_BORDER_BACKGROUND)
+};
+
+void
+nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder,
+                                      nsRenderingContext* aCtx)
+{
+  DrawResult result = static_cast<nsTableFrame*>(mFrame)->
+    PaintTableBorderBackground(aBuilder, *aCtx, mVisibleRect,
+                               ToReferenceFrame());
+
+  nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
 /* static */ void
 nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
                                const nsRect& aDirtyRect, const nsDisplayListSet& aLists)
 {
   // This is similar to what nsContainerFrame::BuildDisplayListForNonBlockChildren
   // does, except that we allow the children's background and borders to go
   // in our BorderBackground list. This doesn't really affect background
   // painting --- the children won't actually draw their own backgrounds
@@ -1480,16 +1511,69 @@ nsTableFrame::GetDeflationForBackground(
   if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() ||
       !IsBorderCollapse())
     return nsMargin(0,0,0,0);
 
   WritingMode wm = GetWritingMode();
   return GetOuterBCBorder(wm).GetPhysicalMargin(wm);
 }
 
+// XXX We don't put the borders and backgrounds in tree order like we should.
+// That requires some major surgery which we aren't going to do right now.
+DrawResult
+nsTableFrame::PaintTableBorderBackground(nsDisplayListBuilder* aBuilder,
+                                         nsRenderingContext& aRenderingContext,
+                                         const nsRect& aDirtyRect,
+                                         nsPoint aPt)
+{
+  nsPresContext* presContext = PresContext();
+
+  uint32_t bgFlags = aBuilder->GetBackgroundPaintFlags();
+  PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
+                               ? PaintBorderFlags::SYNC_DECODE_IMAGES
+                               : PaintBorderFlags();
+
+  TableBackgroundPainter painter(this, TableBackgroundPainter::eOrigin_Table,
+                                 presContext, aRenderingContext,
+                                 aDirtyRect, aPt, bgFlags);
+  nsMargin deflate = GetDeflationForBackground(presContext);
+  // If 'deflate' is (0,0,0,0) then we'll paint the table background
+  // in a separate display item, so don't do it here.
+  DrawResult result =
+    painter.PaintTable(this, deflate, deflate != nsMargin(0, 0, 0, 0));
+
+  if (StyleVisibility()->IsVisible()) {
+    if (!IsBorderCollapse()) {
+      Sides skipSides = GetSkipSides();
+      nsRect rect(aPt, mRect.Size());
+
+      result &=
+        nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+                                    aDirtyRect, rect, mStyleContext,
+                                    borderFlags, skipSides);
+    } else {
+      DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+      gfxPoint devPixelOffset =
+        nsLayoutUtils::PointToGfxPoint(aPt,
+                                       PresContext()->AppUnitsPerDevPixel());
+
+      // XXX we should probably get rid of this translation at some stage
+      // But that would mean modifying PaintBCBorders, ugh
+      AutoRestoreTransform autoRestoreTransform(drawTarget);
+      drawTarget->SetTransform(
+        drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
+
+      PaintBCBorders(*drawTarget, aDirtyRect - aPt);
+    }
+  }
+
+  return result;
+}
+
 nsIFrame::LogicalSides
 nsTableFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
 {
   if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
                      StyleBoxDecorationBreak::Clone)) {
     return LogicalSides();
   }
 
--- a/layout/tables/nsTableFrame.h
+++ b/layout/tables/nsTableFrame.h
@@ -261,16 +261,26 @@ public:
 
   virtual const nsFrameList& GetChildList(ChildListID aListID) const override;
   virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) override;
 
+  /**
+   * Paint the background of the table and its parts (column groups,
+   * columns, row groups, rows, and cells), and the table border, and all
+   * internal borders if border-collapse is on.
+   */
+  DrawResult PaintTableBorderBackground(nsDisplayListBuilder* aBuilder,
+                                        nsRenderingContext& aRenderingContext,
+                                        const nsRect& aDirtyRect,
+                                        nsPoint aPt);
+
   /** Get the outer half (i.e., the part outside the height and width of
    *  the table) of the largest segment (?) of border-collapsed border on
    *  the table on each side, or 0 for non border-collapsed tables.
    */
   LogicalMargin GetOuterBCBorder(const WritingMode aWM) const;
 
   /** Same as above, but only if it's included from the border-box width
    *  of the table.
new file mode 100644
--- /dev/null
+++ b/layout/tables/nsTablePainter.cpp
@@ -0,0 +1,695 @@
+/* -*- 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 "nsTableFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableColGroupFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsTablePainter.h"
+#include "nsCSSRendering.h"
+#include "nsDisplayList.h"
+#include "mozilla/WritingModes.h"
+
+/* ~*~ Table Background Painting ~*~
+
+   Mozilla's Table Background painting follows CSS2.1:17.5.1
+   That section does not, however, describe the effect of
+   borders on background image positioning. What we do is:
+
+     - in separate borders, the borders are passed in so that
+       their width figures in image positioning, even for rows/cols, which
+       don't have visible borders. This is done to allow authors
+       to position row backgrounds by, for example, aligning the
+       top left corner with the top left padding corner of the
+       top left table cell in the row in cases where all cells
+       have consistent border widths. If we didn't honor these
+       invisible borders, there would be no way to align
+       backgrounds with the padding edges, and designs would be
+       lost underneath the border.
+
+     - in collapsing borders, because the borders collapse, we
+       use the -continuous border- width to synthesize a border
+       style and pass that in instead of using the element's
+       assigned style directly.
+
+       The continuous border on a given edge of an element is
+       the collapse of all borders guaranteed to be continuous
+       along that edge. Cell borders are ignored (because, for
+       example, setting a thick border on the leftmost cell
+       should not shift the row background over; this way a
+       striped background set on <tr> will line up across rows
+       even if the cells are assigned arbitrary border widths.
+
+       For example, the continuous border on the top edge of a
+       row group is the collapse of any row group, row, and
+       table borders involved. (The first row group's top would
+       be [table-top + row group top + first row top]. It's bottom
+       would be [row group bottom + last row bottom + next row
+       top + next row group top].)
+       The top edge of a column group likewise includes the
+       table top, row group top, and first row top borders. However,
+       it *also* includes its own top border, since that is guaranteed
+       to be continuous. It does not include column borders because
+       those are not guaranteed to be continuous: there may be two
+       columns with different borders in a single column group.
+
+       An alternative would be to define the continuous border as
+         [table? + row group + row] for horizontal
+         [table? + col group + col] for vertical
+       This makes it easier to line up backgrounds across elements
+       despite varying border widths, but it does not give much
+       flexibility in aligning /to/ those border widths.
+*/
+
+
+/* ~*~ TableBackgroundPainter ~*~
+
+   The TableBackgroundPainter is created and destroyed in one painting call.
+   Its principal function is PaintTable, which paints all table element
+   backgrounds. The initial code in that method sets up an array of column
+   data that caches the background styles and the border sizes for the
+   columns and colgroups in TableBackgroundData structs in mCols. Data for
+   BC borders are calculated and stashed in a synthesized border style struct
+   in the data struct since collapsed borders aren't the same width as style-
+   assigned borders. The data struct optimizes by only doing this if there's
+   an image background; otherwise we don't care. //XXX should also check background-origin
+   The class then loops through the row groups, rows, and cells. At the cell
+   level, it paints the backgrounds, one over the other, inside the cell rect.
+
+   The exception to this pattern is when a table element creates a (pseudo)
+   stacking context. Elements with stacking contexts (e.g., 'opacity' applied)
+   are <dfn>passed through</dfn>, which means their data (and their
+   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.
+
+   XXX views are going
+ */
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+TableBackgroundPainter::TableBackgroundData::TableBackgroundData()
+  : mFrame(nullptr)
+  , mVisible(false)
+  , mUsesSynthBorder(false)
+{
+}
+
+TableBackgroundPainter::TableBackgroundData::TableBackgroundData(nsIFrame* aFrame)
+  : mFrame(aFrame)
+  , mRect(aFrame->GetRect())
+  , mVisible(mFrame->IsVisibleForPainting())
+  , mUsesSynthBorder(false)
+{
+}
+
+inline bool
+TableBackgroundPainter::TableBackgroundData::ShouldSetBCBorder() const
+{
+  /* we only need accurate border data when positioning background images*/
+  if (!mVisible) {
+    return false;
+  }
+
+  const nsStyleImageLayers& layers = mFrame->StyleBackground()->mImage;
+  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
+    if (!layers.mLayers[i].mImage.IsEmpty())
+      return true;
+  }
+  return false;
+}
+
+void
+TableBackgroundPainter::TableBackgroundData::SetBCBorder(const nsMargin& aBorder)
+{
+  mUsesSynthBorder = true;
+  mSynthBorderWidths = aBorder;
+}
+
+nsStyleBorder
+TableBackgroundPainter::TableBackgroundData::StyleBorder(const nsStyleBorder& aZeroBorder) const
+{
+  MOZ_ASSERT(mVisible, "Don't call StyleBorder on an invisible TableBackgroundData");
+
+  if (mUsesSynthBorder) {
+    nsStyleBorder result = aZeroBorder;
+    NS_FOR_CSS_SIDES(side) {
+      result.SetBorderWidth(side, mSynthBorderWidths.Side(side));
+    }
+    return result;
+  }
+
+  MOZ_ASSERT(mFrame);
+
+  return *mFrame->StyleBorder();
+}
+
+TableBackgroundPainter::TableBackgroundPainter(nsTableFrame*        aTableFrame,
+                                               Origin               aOrigin,
+                                               nsPresContext*       aPresContext,
+                                               nsRenderingContext& aRenderingContext,
+                                               const nsRect&        aDirtyRect,
+                                               const nsPoint&       aRenderPt,
+                                               uint32_t             aBGPaintFlags)
+  : mPresContext(aPresContext),
+    mRenderingContext(aRenderingContext),
+    mRenderPt(aRenderPt),
+    mDirtyRect(aDirtyRect),
+    mOrigin(aOrigin),
+    mZeroBorder(aPresContext),
+    mBGPaintFlags(aBGPaintFlags)
+{
+  MOZ_COUNT_CTOR(TableBackgroundPainter);
+
+  NS_FOR_CSS_SIDES(side) {
+    mZeroBorder.SetBorderStyle(side, NS_STYLE_BORDER_STYLE_SOLID);
+    mZeroBorder.SetBorderWidth(side, 0);
+  }
+
+  mIsBorderCollapse = aTableFrame->IsBorderCollapse();
+#ifdef DEBUG
+  mCompatMode = mPresContext->CompatibilityMode();
+#endif
+  mNumCols = aTableFrame->GetColCount();
+}
+
+TableBackgroundPainter::~TableBackgroundPainter()
+{
+  MOZ_COUNT_DTOR(TableBackgroundPainter);
+}
+
+DrawResult
+TableBackgroundPainter::PaintTableFrame(nsTableFrame*         aTableFrame,
+                                        nsTableRowGroupFrame* aFirstRowGroup,
+                                        nsTableRowGroupFrame* aLastRowGroup,
+                                        const nsMargin&       aDeflate)
+{
+  MOZ_ASSERT(aTableFrame, "null frame");
+  TableBackgroundData tableData(aTableFrame);
+  tableData.mRect.MoveTo(0,0); //using table's coords
+  tableData.mRect.Deflate(aDeflate);
+  WritingMode wm = aTableFrame->GetWritingMode();
+  if (mIsBorderCollapse && tableData.ShouldSetBCBorder()) {
+    if (aFirstRowGroup && aLastRowGroup && mNumCols > 0) {
+      //only handle non-degenerate tables; we need a more robust BC model
+      //to make degenerate tables' borders reasonable to deal with
+      LogicalMargin border(wm);
+      LogicalMargin tempBorder(wm);
+      nsTableColFrame* colFrame = aTableFrame->GetColFrame(mNumCols - 1);
+      if (colFrame) {
+        colFrame->GetContinuousBCBorderWidth(wm, tempBorder);
+      }
+      border.IEnd(wm) = tempBorder.IEnd(wm);
+
+      aLastRowGroup->GetContinuousBCBorderWidth(wm, tempBorder);
+      border.BEnd(wm) = tempBorder.BEnd(wm);
+
+      nsTableRowFrame* rowFrame = aFirstRowGroup->GetFirstRow();
+      if (rowFrame) {
+        rowFrame->GetContinuousBCBorderWidth(wm, tempBorder);
+        border.BStart(wm) = tempBorder.BStart(wm);
+      }
+
+      border.IStart(wm) = aTableFrame->GetContinuousIStartBCBorderWidth();
+
+      tableData.SetBCBorder(border.GetPhysicalMargin(wm));
+    }
+  }
+
+  DrawResult result = DrawResult::SUCCESS;
+
+  if (tableData.IsVisible()) {
+    nsCSSRendering::PaintBGParams params =
+      nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext,
+                                                  mDirtyRect,
+                                                  tableData.mRect + mRenderPt,
+                                                  tableData.mFrame,
+                                                  mBGPaintFlags);
+
+    result &=
+      nsCSSRendering::PaintStyleImageLayerWithSC(params, mRenderingContext,
+                                            tableData.mFrame->StyleContext(),
+                                            tableData.StyleBorder(mZeroBorder));
+  }
+
+  return result;
+}
+
+void
+TableBackgroundPainter::TranslateContext(nscoord aDX,
+                                         nscoord aDY)
+{
+  mRenderPt += nsPoint(aDX, aDY);
+  for (auto& col : mCols) {
+    col.mCol.mRect.MoveBy(-aDX, -aDY);
+  }
+  for (auto& colGroup : mColGroups) {
+    colGroup.mRect.MoveBy(-aDX, -aDY);
+  }
+}
+
+TableBackgroundPainter::ColData::ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData)
+  : mCol(aFrame)
+  , mColGroup(aColGroupBGData)
+{
+}
+
+DrawResult
+TableBackgroundPainter::PaintTable(nsTableFrame*   aTableFrame,
+                                   const nsMargin& aDeflate,
+                                   bool            aPaintTableBackground)
+{
+  NS_PRECONDITION(aTableFrame, "null table frame");
+
+  nsTableFrame::RowGroupArray rowGroups;
+  aTableFrame->OrderRowGroups(rowGroups);
+  WritingMode wm = aTableFrame->GetWritingMode();
+
+  DrawResult result = DrawResult::SUCCESS;
+
+  if (rowGroups.Length() < 1) { //degenerate case
+    if (aPaintTableBackground) {
+      result &= PaintTableFrame(aTableFrame, nullptr, nullptr, nsMargin(0,0,0,0));
+    }
+    /* No cells; nothing else to paint */
+    return result;
+  }
+
+  if (aPaintTableBackground) {
+    result &=
+      PaintTableFrame(aTableFrame, rowGroups[0], rowGroups[rowGroups.Length() - 1],
+                      aDeflate);
+  }
+
+  /*Set up column background/border data*/
+  if (mNumCols > 0) {
+    nsFrameList& colGroupList = aTableFrame->GetColGroups();
+    NS_ASSERTION(colGroupList.FirstChild(), "table should have at least one colgroup");
+
+    // Collect all col group frames first so that we know how many there are.
+    nsTArray<nsTableColGroupFrame*> colGroupFrames;
+    for (nsTableColGroupFrame* cgFrame = static_cast<nsTableColGroupFrame*>(colGroupList.FirstChild());
+         cgFrame; cgFrame = static_cast<nsTableColGroupFrame*>(cgFrame->GetNextSibling())) {
+
+      if (cgFrame->GetColCount() < 1) {
+        //No columns, no cells, so no need for data
+        continue;
+      }
+      colGroupFrames.AppendElement(cgFrame);
+    }
+
+    // Ensure that mColGroups won't reallocate during the loop below, because
+    // we grab references to its contents and need those to stay valid until
+    // mColGroups is destroyed as part of TablePainter destruction.
+    mColGroups.SetCapacity(colGroupFrames.Length());
+
+    LogicalMargin border(wm);
+    /* BC iStart borders aren't stored on cols, but the previous column's
+       iEnd border is the next one's iStart border.*/
+    //Start with table's iStart border.
+    nscoord lastIStartBorder = aTableFrame->GetContinuousIStartBCBorderWidth();
+
+    for (nsTableColGroupFrame* cgFrame : colGroupFrames) {
+      /*Create data struct for column group*/
+      TableBackgroundData& cgData = *mColGroups.AppendElement(TableBackgroundData(cgFrame));
+      if (mIsBorderCollapse && cgData.ShouldSetBCBorder()) {
+        border.IStart(wm) = lastIStartBorder;
+        cgFrame->GetContinuousBCBorderWidth(wm, border);
+        cgData.SetBCBorder(border.GetPhysicalMargin(wm));
+      }
+
+      /*Loop over columns in this colgroup*/
+      for (nsTableColFrame* col = cgFrame->GetFirstColumn(); col;
+           col = static_cast<nsTableColFrame*>(col->GetNextSibling())) {
+        MOZ_ASSERT(size_t(col->GetColIndex()) == mCols.Length());
+        // Store a reference to the colGroup in the ColData element.
+        ColData& colData = *mCols.AppendElement(ColData(col, cgData));
+        //Bring column mRect into table's coord system
+        colData.mCol.mRect.MoveBy(cgData.mRect.x, cgData.mRect.y);
+        if (mIsBorderCollapse) {
+          border.IStart(wm) = lastIStartBorder;
+          lastIStartBorder = col->GetContinuousBCBorderWidth(wm, border);
+          if (colData.mCol.ShouldSetBCBorder()) {
+            colData.mCol.SetBCBorder(border.GetPhysicalMargin(wm));
+          }
+        }
+      }
+    }
+  }
+
+  for (uint32_t i = 0; i < rowGroups.Length(); i++) {
+    nsTableRowGroupFrame* rg = rowGroups[i];
+    TableBackgroundData rowGroupBGData(rg);
+    // Need to compute the right rect via GetOffsetTo, since the row
+    // group may not be a child of the table.
+    rowGroupBGData.mRect.MoveTo(rg->GetOffsetTo(aTableFrame));
+
+    // 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)) {
+      result &=
+        PaintRowGroup(rg, rowGroupBGData, rg->IsPseudoStackingContextFromStyle());
+    }
+  }
+
+  return result;
+}
+
+DrawResult
+TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame)
+{
+  return PaintRowGroup(aFrame, TableBackgroundData(aFrame), false);
+}
+
+DrawResult
+TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame,
+                                      TableBackgroundData   aRowGroupBGData,
+                                      bool                  aPassThrough)
+{
+  MOZ_ASSERT(aFrame, "null frame");
+
+  nsTableRowFrame* firstRow = aFrame->GetFirstRow();
+  WritingMode wm = aFrame->GetWritingMode();
+
+  /* Load row group data */
+  if (aPassThrough) {
+    aRowGroupBGData.MakeInvisible();
+  } else {
+    if (mIsBorderCollapse && aRowGroupBGData.ShouldSetBCBorder()) {
+      LogicalMargin border(wm);
+      if (firstRow) {
+        //pick up first row's bstart border (= rg bstart border)
+        firstRow->GetContinuousBCBorderWidth(wm, border);
+        /* (row group doesn't store its bstart border) */
+      }
+      //overwrite sides+bottom borders with rg's own
+      aFrame->GetContinuousBCBorderWidth(wm, border);
+      aRowGroupBGData.SetBCBorder(border.GetPhysicalMargin(wm));
+    }
+    aPassThrough = !aRowGroupBGData.IsVisible();
+  }
+
+  /* translate everything into row group coord system*/
+  if (eOrigin_TableRowGroup != mOrigin) {
+    TranslateContext(aRowGroupBGData.mRect.x, aRowGroupBGData.mRect.y);
+  }
+  nsRect rgRect = aRowGroupBGData.mRect;
+  aRowGroupBGData.mRect.MoveTo(0, 0);
+
+  /* Find the right row to start with */
+
+  // Note that mDirtyRect  - mRenderPt is guaranteed to be in the row
+  // group's coordinate system here, so passing its .y to
+  // GetFirstRowContaining is ok.
+  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->IsTableRowFrame()) {
+    cursor = cursor->GetNextSibling();
+  }
+
+  // It's OK if cursor is null here.
+  nsTableRowFrame* row = static_cast<nsTableRowFrame*>(cursor);
+  if (!row) {
+    // No useful cursor; just start at the top.  Don't bother to set up a
+    // cursor; if we've gotten this far then we've already built the display
+    // 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;
+  }
+
+  DrawResult result = DrawResult::SUCCESS;
+
+  /* Finally paint */
+  for (; row; row = row->GetNextRow()) {
+    TableBackgroundData rowBackgroundData(row);
+
+    // 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(rowBackgroundData.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;
+    }
+
+    result &=
+      PaintRow(row, aRowGroupBGData, rowBackgroundData,
+               aPassThrough || row->IsPseudoStackingContextFromStyle());
+  }
+
+  /* translate back into table coord system */
+  if (eOrigin_TableRowGroup != mOrigin) {
+    TranslateContext(-rgRect.x, -rgRect.y);
+  }
+
+  return result;
+}
+
+DrawResult
+TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame)
+{
+  return PaintRow(aFrame, TableBackgroundData(), TableBackgroundData(aFrame), false);
+}
+
+DrawResult
+TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame,
+                                 const TableBackgroundData& aRowGroupBGData,
+                                 TableBackgroundData aRowBGData,
+                                 bool             aPassThrough)
+{
+  MOZ_ASSERT(aFrame, "null frame");
+
+  /* Load row data */
+  WritingMode wm = aFrame->GetWritingMode();
+  if (aPassThrough) {
+    aRowBGData.MakeInvisible();
+  } else {
+    if (mIsBorderCollapse && aRowBGData.ShouldSetBCBorder()) {
+      LogicalMargin border(wm);
+      nsTableRowFrame* nextRow = aFrame->GetNextRow();
+      if (nextRow) { //outer bStart after us is inner bEnd for us
+        border.BEnd(wm) = nextRow->GetOuterBStartContBCBorderWidth();
+      }
+      else { //acquire rg's bEnd border
+        nsTableRowGroupFrame* rowGroup = static_cast<nsTableRowGroupFrame*>(aFrame->GetParent());
+        rowGroup->GetContinuousBCBorderWidth(wm, border);
+      }
+      //get the rest of the borders; will overwrite all but bEnd
+      aFrame->GetContinuousBCBorderWidth(wm, border);
+
+      aRowBGData.SetBCBorder(border.GetPhysicalMargin(wm));
+    }
+    aPassThrough = !aRowBGData.IsVisible();
+  }
+
+  /* Translate */
+  if (eOrigin_TableRow == mOrigin) {
+    /* If we originate from the row, then make the row the origin. */
+    aRowBGData.mRect.MoveTo(0, 0);
+  }
+  //else: Use row group's coord system -> no translation necessary
+
+  DrawResult result = DrawResult::SUCCESS;
+
+  for (nsTableCellFrame* cell = aFrame->GetFirstCell(); cell; cell = cell->GetNextCell()) {
+    nsRect cellBGRect, rowBGRect, rowGroupBGRect, colBGRect;
+    ComputeCellBackgrounds(cell, aRowGroupBGData, aRowBGData,
+                           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();
+      result &=
+        PaintCell(cell, aRowGroupBGData, aRowBGData, cellBGRect, rowBGRect,
+                  rowGroupBGRect, colBGRect, passCell);
+    }
+  }
+
+  return result;
+}
+
+DrawResult
+TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell,
+                                  const TableBackgroundData& aRowGroupBGData,
+                                  const TableBackgroundData& aRowBGData,
+                                  nsRect&           aCellBGRect,
+                                  nsRect&           aRowBGRect,
+                                  nsRect&           aRowGroupBGRect,
+                                  nsRect&           aColBGRect,
+                                  bool              aPassSelf)
+{
+  MOZ_ASSERT(aCell, "null frame");
+
+  const nsStyleTableBorder* cellTableStyle;
+  cellTableStyle = aCell->StyleTableBorder();
+  if (NS_STYLE_TABLE_EMPTY_CELLS_SHOW != cellTableStyle->mEmptyCells &&
+      aCell->GetContentEmpty() && !mIsBorderCollapse) {
+    return DrawResult::SUCCESS;
+  }
+
+  int32_t colIndex;
+  aCell->GetColIndex(colIndex);
+  // We're checking mNumCols instead of mCols.Length() here because mCols can
+  // be empty even if mNumCols > 0.
+  NS_ASSERTION(size_t(colIndex) < mNumCols, "out-of-bounds column index");
+  if (size_t(colIndex) >= mNumCols) {
+    return DrawResult::SUCCESS;
+  }
+
+  // If callers call PaintRowGroup or PaintRow directly, we haven't processed
+  // our columns. Ignore column / col group backgrounds in that case.
+  bool haveColumns = !mCols.IsEmpty();
+
+  DrawResult result = DrawResult::SUCCESS;
+
+  //Paint column group background
+  if (haveColumns && mCols[colIndex].mColGroup.IsVisible()) {
+    nsCSSRendering::PaintBGParams params =
+      nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext,
+                                                 mDirtyRect,
+                                                 mCols[colIndex].mColGroup.mRect + mRenderPt,
+                                                 mCols[colIndex].mColGroup.mFrame,
+                                                 mBGPaintFlags);
+    params.bgClipRect = &aColBGRect;
+    result &=
+      nsCSSRendering::PaintStyleImageLayerWithSC(params, mRenderingContext,
+                                            mCols[colIndex].mColGroup.mFrame->StyleContext(),
+                                            mCols[colIndex].mColGroup.StyleBorder(mZeroBorder));
+  }
+
+  //Paint column background
+  if (haveColumns && mCols[colIndex].mCol.IsVisible()) {
+    nsCSSRendering::PaintBGParams params =
+      nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext,
+                                                  mDirtyRect,
+                                                  mCols[colIndex].mCol.mRect + mRenderPt,
+                                                  mCols[colIndex].mCol.mFrame,
+                                                  mBGPaintFlags);
+    params.bgClipRect = &aColBGRect;
+    result &=
+      nsCSSRendering::PaintStyleImageLayerWithSC(params, mRenderingContext,
+                                            mCols[colIndex].mCol.mFrame->StyleContext(),
+                                            mCols[colIndex].mCol.StyleBorder(mZeroBorder));
+  }
+
+  //Paint row group background
+  if (aRowGroupBGData.IsVisible()) {
+    nsCSSRendering::PaintBGParams params =
+      nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext,
+                                                  mDirtyRect,
+                                                  aRowGroupBGData.mRect + mRenderPt,
+                                                  aRowGroupBGData.mFrame, mBGPaintFlags);
+    params.bgClipRect = &aRowGroupBGRect;
+    result &=
+      nsCSSRendering::PaintStyleImageLayerWithSC(params, mRenderingContext,
+                                            aRowGroupBGData.mFrame->StyleContext(),
+                                            aRowGroupBGData.StyleBorder(mZeroBorder));
+  }
+
+  //Paint row background
+  if (aRowBGData.IsVisible()) {
+    nsCSSRendering::PaintBGParams params =
+      nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext,
+                                                  mDirtyRect,
+                                                  aRowBGData.mRect + mRenderPt,
+                                                  aRowBGData.mFrame, mBGPaintFlags);
+    params.bgClipRect = &aRowBGRect;
+    result &=
+      nsCSSRendering::PaintStyleImageLayerWithSC(params, mRenderingContext,
+                                            aRowBGData.mFrame->StyleContext(),
+                                            aRowBGData.StyleBorder(mZeroBorder));
+  }
+
+  //Paint cell background in border-collapse unless we're just passing
+  if (mIsBorderCollapse && !aPassSelf) {
+    result &=
+      aCell->PaintCellBackground(mRenderingContext, mDirtyRect,
+                                 aCellBGRect.TopLeft(), mBGPaintFlags);
+  }
+
+  return result;
+}
+
+void
+TableBackgroundPainter::ComputeCellBackgrounds(nsTableCellFrame* aCell,
+                                               const TableBackgroundData& aRowGroupBGData,
+                                               const TableBackgroundData& aRowBGData,
+                                               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 =
+    aRowGroupBGData.mFrame ? aRowGroupBGData.mFrame : aRowBGData.mFrame->GetParent();
+
+  // The cell background goes at the cell's position, translated to use the same
+  // coordinate system as aRowBGData.
+  aCellBGRect = aCell->GetRect() + aRowBGData.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 +
+                    (aRowBGData.mFrame->GetNormalPosition() - aRowBGData.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());
+
+}
new file mode 100644
--- /dev/null
+++ b/layout/tables/nsTablePainter.h
@@ -0,0 +1,268 @@
+/* -*- 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/. */
+
+#ifndef nsTablePainter_h__
+#define nsTablePainter_h__
+
+#include "imgIContainer.h"
+
+#include "celldata.h"
+
+// flags for Paint, PaintChild, PaintChildren are currently only used by tables.
+//Table-based paint call; not a direct call as with views
+#define NS_PAINT_FLAG_TABLE_BG_PAINT      0x00000001
+//Cells should paint their backgrounds only, no children
+#define NS_PAINT_FLAG_TABLE_CELL_BG_PASS  0x00000002
+
+class nsIFrame;
+class nsTableFrame;
+class nsTableRowGroupFrame;
+class nsTableRowFrame;
+class nsTableCellFrame;
+
+class TableBackgroundPainter
+{
+  /*
+   * Helper class for painting table backgrounds
+   *
+   */
+
+  typedef mozilla::image::DrawResult DrawResult;
+
+  public:
+
+    enum Origin { eOrigin_Table, eOrigin_TableRowGroup, eOrigin_TableRow };
+
+    /** Public constructor
+      * @param aTableFrame       - the table's table frame
+      * @param aOrigin           - what type of table frame is creating this instance
+      * @param aPresContext      - the presentation context
+      * @param aRenderingContext - the rendering context
+      * @param aDirtyRect        - the area that needs to be painted,
+      * relative to aRenderingContext
+      * @param aPt               - offset of the table frame relative to
+      * aRenderingContext
+      * @param aBGPaintFlags - Flags of the nsCSSRendering::PAINTBG_* variety
+      */
+    TableBackgroundPainter(nsTableFrame*        aTableFrame,
+                           Origin               aOrigin,
+                           nsPresContext*       aPresContext,
+                           nsRenderingContext& aRenderingContext,
+                           const nsRect&        aDirtyRect,
+                           const nsPoint&       aPt,
+                           uint32_t             aBGPaintFlags);
+
+    /** Destructor */
+    ~TableBackgroundPainter();
+
+    /* ~*~ The Border Collapse Painting Issue ~*~
+
+       In border-collapse, the *table* paints the cells' borders,
+       so we need to make sure the backgrounds get painted first
+       (underneath) by doing a cell-background-only painting pass.
+    */
+
+    /* ~*~ Using nsTablePainter Background Painting ~*~
+
+       A call to PaintTable will normally paint all of the table's
+       elements (except for the table background, if aPaintTableBackground
+       is false).
+       Elements with views however, will be skipped and must create their
+       own painter to call the appropriate paint function in their ::Paint
+       method (e.g. painter.PaintRow in nsTableRow::Paint)
+    */
+
+    /** Paint background for the table frame (if requested) and its children
+      * down through cells.
+      * (Cells themselves will only be painted in border collapse)
+      * Table must do a flagged TABLE_BG_PAINT ::Paint call on its
+      * children afterwards
+      * @param aTableFrame - the table frame
+      * @param aDeflate    - deflation needed to bring table's mRect
+      *                      to the outer grid lines in border-collapse
+      * @param aPaintTableBackground - if true, the table background
+      * is included, otherwise it isn't
+      * @returns DrawResult::SUCCESS if all painting was successful. If some
+      *          painting failed or an improved result could be achieved by sync
+      *          decoding images, returns another value.
+      */
+    DrawResult PaintTable(nsTableFrame* aTableFrame, const nsMargin& aDeflate,
+                          bool aPaintTableBackground);
+
+    /** Paint background for the row group and its children down through cells
+      * (Cells themselves will only be painted in border collapse)
+      * Standards mode only
+      * Table Row Group must do a flagged TABLE_BG_PAINT ::Paint call on its
+      * children afterwards
+      * @param aFrame - the table row group frame
+      * @returns DrawResult::SUCCESS if all painting was successful. If some
+      *          painting failed or an improved result could be achieved by sync
+      *          decoding images, returns another value.
+      */
+    DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame);
+
+    /** Paint background for the row and its children down through cells
+      * (Cells themselves will only be painted in border collapse)
+      * Standards mode only
+      * Table Row must do a flagged TABLE_BG_PAINT ::Paint call on its
+      * children afterwards
+      * @param aFrame - the table row frame
+      * @returns DrawResult::SUCCESS if all painting was successful. If some
+      *          painting failed or an improved result could be achieved by sync
+      *          decoding images, returns another value.
+      */
+    DrawResult PaintRow(nsTableRowFrame* aFrame);
+
+  private:
+    struct TableBackgroundData;
+
+    /** Paint table frame's background
+      * @param aTableFrame     - the table frame
+      * @param aFirstRowGroup  - the first (in layout order) row group
+      *                          may be null
+      * @param aLastRowGroup   - the last (in layout order) row group
+      *                          may be null
+      * @param aDeflate        - adjustment to frame's rect (used for quirks BC)
+      *                          may be null
+      */
+    DrawResult PaintTableFrame(nsTableFrame*         aTableFrame,
+                               nsTableRowGroupFrame* aFirstRowGroup,
+                               nsTableRowGroupFrame* aLastRowGroup,
+                               const nsMargin&       aDeflate);
+
+    /* aPassThrough params indicate whether to paint the element or to just
+     * pass through and paint underlying layers only.
+     * aRowGroupBGData is not a const reference because the function modifies
+     * its copy. Same for aRowBGData in PaintRow.
+     * See Public versions for function descriptions
+     */
+    DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame,
+                             TableBackgroundData   aRowGroupBGData,
+                             bool                  aPassThrough);
+
+    DrawResult PaintRow(nsTableRowFrame* aFrame,
+                        const TableBackgroundData& aRowGroupBGData,
+                        TableBackgroundData aRowBGData,
+                        bool             aPassThrough);
+
+    /** Paint table background layers for this cell space
+      * Also paints cell's own background in border-collapse mode
+      * @param aCell           - the cell
+      * @param aRowGroupBGData - background drawing info for the row group
+      * @param aRowBGData      - background drawing info for the row
+      * @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
+      */
+    DrawResult PaintCell(nsTableCellFrame* aCell,
+                         const TableBackgroundData& aRowGroupBGData,
+                         const TableBackgroundData& aRowBGData,
+                         nsRect&           aCellBGRect,
+                         nsRect&           aRowBGRect,
+                         nsRect&           aRowGroupBGRect,
+                         nsRect&           aColBGRect,
+                         bool              aPassSelf);
+
+    /** Compute table background layer positions for this cell space
+      * @param aCell              - the cell
+      * @param aRowGroupBGData    - background drawing info for the row group
+      * @param aRowBGData         - background drawing info for the row
+      * @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,
+                                const TableBackgroundData& aRowGroupBGData,
+                                const TableBackgroundData& aRowBGData,
+                                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);
+
+    struct TableBackgroundData {
+    public:
+      /**
+       * Construct an empty TableBackgroundData instance, which is invisible.
+       */
+      TableBackgroundData();
+
+      /**
+       * Construct a TableBackgroundData instance for a frame. Visibility will
+       * be derived from the frame and can be overridden using MakeInvisible().
+       */
+      explicit TableBackgroundData(nsIFrame* aFrame);
+
+      /** Destructor */
+      ~TableBackgroundData() {}
+
+      /** Data is valid & frame is visible */
+      bool IsVisible() const { return mVisible; }
+
+      /** Override visibility of the frame, force it to be invisible */
+      void MakeInvisible() { mVisible = false; }
+
+      /** True if need to set border-collapse border; must call SetFull beforehand */
+      bool ShouldSetBCBorder() const;
+
+      /** Set border-collapse border with aBorderWidth as widths */
+      void SetBCBorder(const nsMargin& aBorderWidth);
+
+      /**
+       * @param  aZeroBorder An nsStyleBorder instance that has been initialized
+       *                     for the right nsPresContext, with all border widths
+       *                     set to zero and border styles set to solid.
+       * @return             The nsStyleBorder that should be used for rendering
+       *                     this background.
+       */
+      nsStyleBorder StyleBorder(const nsStyleBorder& aZeroBorder) const;
+
+      nsIFrame* const mFrame;
+
+      /** mRect is the rect of mFrame in the current coordinate system */
+      nsRect mRect;
+
+    private:
+      nsMargin mSynthBorderWidths;
+      bool mVisible;
+      bool mUsesSynthBorder;
+    };
+
+    struct ColData {
+      ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData);
+      TableBackgroundData mCol;
+      TableBackgroundData& mColGroup; // reference to col's parent colgroup's data, owned by TablePainter in mColGroups
+    };
+
+    nsPresContext*      mPresContext;
+    nsRenderingContext& mRenderingContext;
+    nsPoint              mRenderPt;
+    nsRect               mDirtyRect;
+#ifdef DEBUG
+    nsCompatibility      mCompatMode;
+#endif
+    bool                 mIsBorderCollapse;
+    Origin               mOrigin; //user's table frame type
+
+    nsTArray<TableBackgroundData> mColGroups;
+    nsTArray<ColData>    mCols;
+    size_t               mNumCols;
+
+    nsStyleBorder        mZeroBorder;  //cached zero-width border
+    uint32_t             mBGPaintFlags;
+};
+
+#endif
--- a/layout/tables/nsTableRowFrame.cpp
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -572,16 +572,55 @@ nsTableRowFrame::CalcBSize(const ReflowI
          ascent = cellFrame->GetCellBaseline();
        nscoord descent = desSize.BSize(wm) - ascent;
        UpdateBSize(desSize.BSize(wm), ascent, descent, tableFrame, cellFrame);
     }
   }
   return GetInitialBSize();
 }
 
+/**
+ * We need a custom display item for table row backgrounds. This is only used
+ * when the table row is the root of a stacking context (e.g., has 'opacity').
+ * Table row backgrounds can extend beyond the row frame bounds, when
+ * the row contains row-spanning cells.
+ */
+class nsDisplayTableRowBackground : public nsDisplayTableItem {
+public:
+  nsDisplayTableRowBackground(nsDisplayListBuilder* aBuilder,
+                              nsTableRowFrame*      aFrame) :
+    nsDisplayTableItem(aBuilder, aFrame) {
+    MOZ_COUNT_CTOR(nsDisplayTableRowBackground);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayTableRowBackground() {
+    MOZ_COUNT_DTOR(nsDisplayTableRowBackground);
+  }
+#endif
+
+  virtual void Paint(nsDisplayListBuilder* aBuilder,
+                     nsRenderingContext*   aCtx) override;
+  NS_DISPLAY_DECL_NAME("TableRowBackground", TYPE_TABLE_ROW_BACKGROUND)
+};
+
+void
+nsDisplayTableRowBackground::Paint(nsDisplayListBuilder* aBuilder,
+                                   nsRenderingContext*   aCtx)
+{
+  auto rowFrame = static_cast<nsTableRowFrame*>(mFrame);
+  TableBackgroundPainter painter(rowFrame->GetTableFrame(),
+                                 TableBackgroundPainter::eOrigin_TableRow,
+                                 mFrame->PresContext(), *aCtx,
+                                 mVisibleRect, ToReferenceFrame(),
+                                 aBuilder->GetBackgroundPaintFlags());
+
+  DrawResult result = painter.PaintRow(rowFrame);
+  nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
 void
 nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                   const nsRect&           aDirtyRect,
                                   const nsDisplayListSet& aLists)
 {
   nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists);
 }
 
--- a/layout/tables/nsTableRowFrame.h
+++ b/layout/tables/nsTableRowFrame.h
@@ -3,16 +3,17 @@
  * 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/. */
 #ifndef nsTableRowFrame_h__
 #define nsTableRowFrame_h__
 
 #include "mozilla/Attributes.h"
 #include "nscore.h"
 #include "nsContainerFrame.h"
+#include "nsTablePainter.h"
 #include "nsTableRowGroupFrame.h"
 #include "mozilla/WritingModes.h"
 
 class  nsTableCellFrame;
 namespace mozilla {
 struct TableCellReflowInput;
 } // namespace mozilla
 
--- a/layout/tables/nsTableRowGroupFrame.cpp
+++ b/layout/tables/nsTableRowGroupFrame.cpp
@@ -193,16 +193,56 @@ nsTableRowGroupFrame::InitRepeatedFrame(
     // Move to the next row frame
     originalRowFrame = originalRowFrame->GetNextRow();
     copyRowFrame = copyRowFrame->GetNextRow();
   }
 
   return NS_OK;
 }
 
+/**
+ * We need a custom display item for table row backgrounds. This is only used
+ * when the table row is the root of a stacking context (e.g., has 'opacity').
+ * Table row backgrounds can extend beyond the row frame bounds, when
+ * the row contains row-spanning cells.
+ */
+class nsDisplayTableRowGroupBackground : public nsDisplayTableItem {
+public:
+  nsDisplayTableRowGroupBackground(nsDisplayListBuilder* aBuilder,
+                                   nsTableRowGroupFrame* aFrame) :
+    nsDisplayTableItem(aBuilder, aFrame) {
+    MOZ_COUNT_CTOR(nsDisplayTableRowGroupBackground);
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayTableRowGroupBackground() {
+    MOZ_COUNT_DTOR(nsDisplayTableRowGroupBackground);
+  }
+#endif
+
+  virtual void Paint(nsDisplayListBuilder* aBuilder,
+                     nsRenderingContext* aCtx) override;
+
+  NS_DISPLAY_DECL_NAME("TableRowGroupBackground", TYPE_TABLE_ROW_GROUP_BACKGROUND)
+};
+
+void
+nsDisplayTableRowGroupBackground::Paint(nsDisplayListBuilder* aBuilder,
+                                        nsRenderingContext* aCtx)
+{
+  auto rgFrame = static_cast<nsTableRowGroupFrame*>(mFrame);
+  TableBackgroundPainter painter(rgFrame->GetTableFrame(),
+                                 TableBackgroundPainter::eOrigin_TableRowGroup,
+                                 mFrame->PresContext(), *aCtx,
+                                 mVisibleRect, ToReferenceFrame(),
+                                 aBuilder->GetBackgroundPaintFlags());
+
+  DrawResult result = painter.PaintRowGroup(rgFrame);
+  nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
 // Handle the child-traversal part of DisplayGenericTablePart
 static void
 DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
             const nsRect& aDirtyRect, const nsDisplayListSet& aLists)
 {
   nscoord overflowAbove;
   nsTableRowGroupFrame* f = static_cast<nsTableRowGroupFrame*>(aFrame);
   // Don't try to use the row cursor if we have to descend into placeholders;
--- a/layout/tables/nsTableRowGroupFrame.h
+++ b/layout/tables/nsTableRowGroupFrame.h
@@ -5,16 +5,17 @@
 #ifndef nsTableRowGroupFrame_h__
 #define nsTableRowGroupFrame_h__
 
 #include "mozilla/Attributes.h"
 #include "nscore.h"
 #include "nsContainerFrame.h"
 #include "nsIAtom.h"
 #include "nsILineIterator.h"
+#include "nsTablePainter.h"
 #include "nsTArray.h"
 #include "nsTableFrame.h"
 #include "mozilla/WritingModes.h"
 
 class nsTableRowFrame;
 namespace mozilla {
 struct TableRowGroupReflowInput;
 } // namespace mozilla