tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsTableFrame.cpp (288180B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sw=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsTableFrame.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "BasicTableLayoutStrategy.h"
     12 #include "FixedTableLayoutStrategy.h"
     13 #include "gfxContext.h"
     14 #include "mozilla/ComputedStyle.h"
     15 #include "mozilla/IntegerRange.h"
     16 #include "mozilla/Likely.h"
     17 #include "mozilla/MathAlgorithms.h"
     18 #include "mozilla/PresShell.h"
     19 #include "mozilla/PresShellInlines.h"
     20 #include "mozilla/Range.h"
     21 #include "mozilla/RestyleManager.h"
     22 #include "mozilla/ServoStyleSet.h"
     23 #include "mozilla/WritingModes.h"
     24 #include "mozilla/gfx/2D.h"
     25 #include "mozilla/gfx/Helpers.h"
     26 #include "mozilla/layers/RenderRootStateManager.h"
     27 #include "mozilla/layers/StackingContextHelper.h"
     28 #include "nsCOMPtr.h"
     29 #include "nsCSSAnonBoxes.h"
     30 #include "nsCSSFrameConstructor.h"
     31 #include "nsCSSProps.h"
     32 #include "nsCSSRendering.h"
     33 #include "nsCellMap.h"
     34 #include "nsContentUtils.h"
     35 #include "nsDisplayList.h"
     36 #include "nsError.h"
     37 #include "nsFrameList.h"
     38 #include "nsFrameManager.h"
     39 #include "nsGkAtoms.h"
     40 #include "nsHTMLParts.h"
     41 #include "nsIContent.h"
     42 #include "nsIFrameInlines.h"
     43 #include "nsIScriptError.h"
     44 #include "nsLayoutUtils.h"
     45 #include "nsPresContext.h"
     46 #include "nsStyleChangeList.h"
     47 #include "nsStyleConsts.h"
     48 #include "nsTableCellFrame.h"
     49 #include "nsTableColFrame.h"
     50 #include "nsTableColGroupFrame.h"
     51 #include "nsTableRowFrame.h"
     52 #include "nsTableRowGroupFrame.h"
     53 #include "nsTableWrapperFrame.h"
     54 
     55 using namespace mozilla;
     56 using namespace mozilla::image;
     57 using namespace mozilla::layout;
     58 
     59 using mozilla::gfx::AutoRestoreTransform;
     60 using mozilla::gfx::DrawTarget;
     61 using mozilla::gfx::Float;
     62 using mozilla::gfx::ToDeviceColor;
     63 
     64 namespace mozilla {
     65 
     66 struct TableReflowInput final {
     67  TableReflowInput(const ReflowInput& aReflowInput,
     68                   const LogicalMargin& aBorderPadding, TableReflowMode aMode)
     69      : mReflowInput(aReflowInput),
     70        mWM(aReflowInput.GetWritingMode()),
     71        mAvailSize(mWM) {
     72    MOZ_ASSERT(mReflowInput.mFrame->IsTableFrame(),
     73               "TableReflowInput should only be created for nsTableFrame");
     74    auto* table = static_cast<nsTableFrame*>(mReflowInput.mFrame);
     75 
     76    mICoord = aBorderPadding.IStart(mWM) + table->GetColSpacing(-1);
     77    mAvailSize.ISize(mWM) =
     78        std::max(0, mReflowInput.ComputedISize() - table->GetColSpacing(-1) -
     79                        table->GetColSpacing(table->GetColCount()));
     80 
     81    mAvailSize.BSize(mWM) = aMode == TableReflowMode::Measuring
     82                                ? NS_UNCONSTRAINEDSIZE
     83                                : mReflowInput.AvailableBSize();
     84    AdvanceBCoord(aBorderPadding.BStart(mWM) +
     85                  (!table->GetPrevInFlow() ? table->GetRowSpacing(-1) : 0));
     86    if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
     87        StyleBoxDecorationBreak::Clone) {
     88      // At this point, we're assuming we won't be the last fragment, so we only
     89      // reserve space for block-end border-padding if we're cloning it on each
     90      // fragment; and we don't need to reserve any row-spacing for this
     91      // hypothetical fragmentation, either.
     92      ReduceAvailableBSizeBy(aBorderPadding.BEnd(mWM));
     93    }
     94  }
     95 
     96  // Advance to the next block-offset and reduce the available block-size.
     97  void AdvanceBCoord(nscoord aAmount) {
     98    mBCoord += aAmount;
     99    ReduceAvailableBSizeBy(aAmount);
    100  }
    101 
    102  const LogicalSize& AvailableSize() const { return mAvailSize; }
    103 
    104  // The real reflow input of the table frame.
    105  const ReflowInput& mReflowInput;
    106 
    107  // Stationary inline-offset, which won't change after the constructor.
    108  nscoord mICoord = 0;
    109 
    110  // Running block-offset, which will be adjusted as we reflow children.
    111  nscoord mBCoord = 0;
    112 
    113 private:
    114  void ReduceAvailableBSizeBy(nscoord aAmount) {
    115    if (mAvailSize.BSize(mWM) == NS_UNCONSTRAINEDSIZE) {
    116      return;
    117    }
    118    mAvailSize.BSize(mWM) -= aAmount;
    119    mAvailSize.BSize(mWM) = std::max(0, mAvailSize.BSize(mWM));
    120  }
    121 
    122  // mReflowInput's (i.e. table frame's) writing-mode.
    123  WritingMode mWM;
    124 
    125  // The available size for children. The inline-size is stationary after the
    126  // constructor, but the block-size will be adjusted as we reflow children.
    127  LogicalSize mAvailSize;
    128 };
    129 
    130 struct TableBCData final {
    131  TableArea mDamageArea;
    132  nscoord mBStartBorderWidth = 0;
    133  nscoord mIEndBorderWidth = 0;
    134  nscoord mBEndBorderWidth = 0;
    135  nscoord mIStartBorderWidth = 0;
    136 };
    137 
    138 }  // namespace mozilla
    139 
    140 /********************************************************************************
    141 ** nsTableFrame **
    142 ********************************************************************************/
    143 
    144 ComputedStyle* nsTableFrame::GetParentComputedStyle(
    145    nsIFrame** aProviderFrame) const {
    146  // Since our parent, the table wrapper frame, returned this frame, we
    147  // must return whatever our parent would normally have returned.
    148 
    149  MOZ_ASSERT(GetParent(), "table constructed without table wrapper");
    150  if (!mContent->GetParent() && !Style()->IsPseudoOrAnonBox()) {
    151    // We're the root.  We have no ComputedStyle parent.
    152    *aProviderFrame = nullptr;
    153    return nullptr;
    154  }
    155 
    156  return GetParent()->DoGetParentComputedStyle(aProviderFrame);
    157 }
    158 
    159 nsTableFrame::nsTableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
    160                           ClassID aID)
    161    : nsContainerFrame(aStyle, aPresContext, aID) {
    162  memset(&mBits, 0, sizeof(mBits));
    163 }
    164 
    165 void nsTableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
    166                        nsIFrame* aPrevInFlow) {
    167  MOZ_ASSERT(!mCellMap, "Init called twice");
    168  MOZ_ASSERT(!mTableLayoutStrategy, "Init called twice");
    169  MOZ_ASSERT(!aPrevInFlow || aPrevInFlow->IsTableFrame(),
    170             "prev-in-flow must be of same type");
    171 
    172  // Let the base class do its processing
    173  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
    174 
    175  // see if border collapse is on, if so set it
    176  const nsStyleTableBorder* tableStyle = StyleTableBorder();
    177  bool borderCollapse =
    178      (StyleBorderCollapse::Collapse == tableStyle->mBorderCollapse);
    179  SetBorderCollapse(borderCollapse);
    180  if (borderCollapse) {
    181    SetNeedToCalcHasBCBorders(true);
    182  }
    183 
    184  if (!aPrevInFlow) {
    185    // If we're the first-in-flow, we manage the cell map & layout strategy that
    186    // get used by our continuation chain:
    187    mCellMap = MakeUnique<nsTableCellMap>(*this, borderCollapse);
    188    if (IsAutoLayout()) {
    189      mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this);
    190    } else {
    191      mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this);
    192    }
    193  } else {
    194    // Set my isize, because all frames in a table flow are the same isize and
    195    // code in nsTableWrapperFrame depends on this being set.
    196    WritingMode wm = GetWritingMode();
    197    SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm)));
    198  }
    199 }
    200 
    201 // Define here (Rather than in the header), even if it's trival, to avoid
    202 // UniquePtr members causing compile errors when their destructors are
    203 // implicitly inserted into this destructor. Destruction requires
    204 // the full definition of types that these UniquePtrs are managing, and
    205 // the header only has forward declarations of them.
    206 nsTableFrame::~nsTableFrame() = default;
    207 
    208 void nsTableFrame::Destroy(DestroyContext& aContext) {
    209  MOZ_ASSERT(!mBits.mIsDestroying);
    210  mBits.mIsDestroying = true;
    211  mColGroups.DestroyFrames(aContext);
    212  nsContainerFrame::Destroy(aContext);
    213 }
    214 
    215 static bool IsRepeatedFrame(nsIFrame* kidFrame) {
    216  return (kidFrame->IsTableRowFrame() || kidFrame->IsTableRowGroupFrame()) &&
    217         kidFrame->HasAnyStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
    218 }
    219 
    220 bool nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame,
    221                                  nsIFrame* aNextFrame) {
    222  const nsStyleDisplay* display = aSourceFrame->StyleDisplay();
    223  nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame);
    224  // don't allow a page break after a repeated element ...
    225  if ((display->BreakAfter() || (prevRg && prevRg->HasInternalBreakAfter())) &&
    226      !IsRepeatedFrame(aSourceFrame)) {
    227    return !(aNextFrame && IsRepeatedFrame(aNextFrame));  // or before
    228  }
    229 
    230  if (aNextFrame) {
    231    display = aNextFrame->StyleDisplay();
    232    // don't allow a page break before a repeated element ...
    233    nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame);
    234    if ((display->BreakBefore() ||
    235         (nextRg && nextRg->HasInternalBreakBefore())) &&
    236        !IsRepeatedFrame(aNextFrame)) {
    237      return !IsRepeatedFrame(aSourceFrame);  // or after
    238    }
    239  }
    240  return false;
    241 }
    242 
    243 /* static */
    244 void nsTableFrame::PositionedTablePartMaybeChanged(nsContainerFrame* aFrame,
    245                                                   ComputedStyle* aOldStyle) {
    246  const bool wasPositioned =
    247      aOldStyle && aOldStyle->IsAbsPosContainingBlock(aFrame);
    248  const bool isPositioned = aFrame->IsAbsPosContainingBlock();
    249  MOZ_ASSERT(isPositioned == aFrame->Style()->IsAbsPosContainingBlock(aFrame));
    250  if (wasPositioned == isPositioned) {
    251    return;
    252  }
    253 
    254  nsTableFrame* tableFrame = GetTableFrame(aFrame);
    255  MOZ_ASSERT(tableFrame, "Should have a table frame here");
    256  tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
    257 
    258  // Retrieve the positioned parts array for this table.
    259  TablePartsArray* positionedParts =
    260      tableFrame->GetProperty(PositionedTablePartsProperty());
    261 
    262  // Lazily create the array if it doesn't exist yet.
    263  if (!positionedParts) {
    264    positionedParts = new TablePartsArray;
    265    tableFrame->SetProperty(PositionedTablePartsProperty(), positionedParts);
    266  }
    267 
    268  if (isPositioned) {
    269    // Add this frame to the list.
    270    positionedParts->AppendElement(aFrame);
    271  } else {
    272    positionedParts->RemoveElement(aFrame);
    273  }
    274 }
    275 
    276 /* static */
    277 void nsTableFrame::MaybeUnregisterPositionedTablePart(
    278    nsContainerFrame* aFrame) {
    279  if (!aFrame->IsAbsPosContainingBlock()) {
    280    return;
    281  }
    282  nsTableFrame* tableFrame = GetTableFrame(aFrame);
    283  tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
    284 
    285  if (tableFrame->IsDestroying()) {
    286    return;  // We're throwing the table away anyways.
    287  }
    288 
    289  // Retrieve the positioned parts array for this table.
    290  TablePartsArray* positionedParts =
    291      tableFrame->GetProperty(PositionedTablePartsProperty());
    292 
    293  // Remove the frame.
    294  MOZ_ASSERT(
    295      positionedParts && positionedParts->Contains(aFrame),
    296      "Asked to unregister a positioned table part that wasn't registered");
    297  if (positionedParts) {
    298    positionedParts->RemoveElement(aFrame);
    299  }
    300 }
    301 
    302 // XXX this needs to be cleaned up so that the frame constructor breaks out col
    303 // group frames into a separate child list, bug 343048.
    304 void nsTableFrame::SetInitialChildList(ChildListID aListID,
    305                                       nsFrameList&& aChildList) {
    306  if (aListID != FrameChildListID::Principal) {
    307    nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
    308    return;
    309  }
    310 
    311  MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(),
    312             "unexpected second call to SetInitialChildList");
    313 #ifdef DEBUG
    314  for (nsIFrame* f : aChildList) {
    315    MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
    316  }
    317 #endif
    318 
    319  // XXXbz the below code is an icky cesspit that's only needed in its current
    320  // form for two reasons:
    321  // 1) Both rowgroups and column groups come in on the principal child list.
    322  while (aChildList.NotEmpty()) {
    323    nsIFrame* childFrame = aChildList.FirstChild();
    324    aChildList.RemoveFirstChild();
    325    const nsStyleDisplay* childDisplay = childFrame->StyleDisplay();
    326 
    327    if (mozilla::StyleDisplay::TableColumnGroup == childDisplay->mDisplay) {
    328      NS_ASSERTION(childFrame->IsTableColGroupFrame(),
    329                   "This is not a colgroup");
    330      mColGroups.AppendFrame(nullptr, childFrame);
    331    } else {  // row groups and unknown frames go on the main list for now
    332      mFrames.AppendFrame(nullptr, childFrame);
    333    }
    334  }
    335 
    336  // If we have a prev-in-flow, then we're a table that has been split and
    337  // so don't treat this like an append
    338  if (!GetPrevInFlow()) {
    339    // process col groups first so that real cols get constructed before
    340    // anonymous ones due to cells in rows.
    341    InsertColGroups(0, mColGroups);
    342    InsertRowGroups(mFrames);
    343    // calc collapsing borders
    344    if (IsBorderCollapse()) {
    345      SetFullBCDamageArea();
    346    }
    347  }
    348 }
    349 
    350 void nsTableFrame::RowOrColSpanChanged(nsTableCellFrame* aCellFrame) {
    351  if (aCellFrame) {
    352    nsTableCellMap* cellMap = GetCellMap();
    353    if (cellMap) {
    354      // for now just remove the cell from the map and reinsert it
    355      uint32_t rowIndex = aCellFrame->RowIndex();
    356      uint32_t colIndex = aCellFrame->ColIndex();
    357      RemoveCell(aCellFrame, rowIndex);
    358      AutoTArray<nsTableCellFrame*, 1> cells;
    359      cells.AppendElement(aCellFrame);
    360      InsertCells(cells, rowIndex, colIndex - 1);
    361 
    362      // XXX Should this use IntrinsicDirty::FrameAncestorsAndDescendants? It
    363      // currently doesn't need to, but it might given more optimization.
    364      PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
    365                                    NS_FRAME_IS_DIRTY);
    366    }
    367  }
    368 }
    369 
    370 /* ****** CellMap methods ******* */
    371 
    372 /* return the effective col count */
    373 int32_t nsTableFrame::GetEffectiveColCount() const {
    374  int32_t colCount = GetColCount();
    375  if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) {
    376    nsTableCellMap* cellMap = GetCellMap();
    377    if (!cellMap) {
    378      return 0;
    379    }
    380    // don't count cols at the end that don't have originating cells
    381    for (int32_t colIdx = colCount - 1; colIdx >= 0; colIdx--) {
    382      if (cellMap->GetNumCellsOriginatingInCol(colIdx) > 0) {
    383        break;
    384      }
    385      colCount--;
    386    }
    387  }
    388  return colCount;
    389 }
    390 
    391 int32_t nsTableFrame::GetIndexOfLastRealCol() {
    392  int32_t numCols = mColFrames.Length();
    393  if (numCols > 0) {
    394    for (int32_t colIdx = numCols - 1; colIdx >= 0; colIdx--) {
    395      nsTableColFrame* colFrame = GetColFrame(colIdx);
    396      if (colFrame) {
    397        if (eColAnonymousCell != colFrame->GetColType()) {
    398          return colIdx;
    399        }
    400      }
    401    }
    402  }
    403  return -1;
    404 }
    405 
    406 nsTableColFrame* nsTableFrame::GetColFrame(int32_t aColIndex) const {
    407  MOZ_ASSERT(!GetPrevInFlow(), "GetColFrame called on next in flow");
    408  int32_t numCols = mColFrames.Length();
    409  if ((aColIndex >= 0) && (aColIndex < numCols)) {
    410    MOZ_ASSERT(mColFrames.ElementAt(aColIndex));
    411    return mColFrames.ElementAt(aColIndex);
    412  } else {
    413    MOZ_ASSERT_UNREACHABLE("invalid col index");
    414    return nullptr;
    415  }
    416 }
    417 
    418 int32_t nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex,
    419                                          const nsTableCellFrame& aCell) const {
    420  nsTableCellMap* cellMap = GetCellMap();
    421  MOZ_ASSERT(nullptr != cellMap, "bad call, cellMap not yet allocated.");
    422 
    423  return cellMap->GetEffectiveRowSpan(aRowIndex, aCell.ColIndex());
    424 }
    425 
    426 int32_t nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell,
    427                                          nsCellMap* aCellMap) {
    428  nsTableCellMap* tableCellMap = GetCellMap();
    429  if (!tableCellMap) ABORT1(1);
    430 
    431  uint32_t colIndex = aCell.ColIndex();
    432  uint32_t rowIndex = aCell.RowIndex();
    433 
    434  if (aCellMap) {
    435    return aCellMap->GetRowSpan(rowIndex, colIndex, true);
    436  }
    437  return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex);
    438 }
    439 
    440 int32_t nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell,
    441                                          nsCellMap* aCellMap) const {
    442  nsTableCellMap* tableCellMap = GetCellMap();
    443  if (!tableCellMap) ABORT1(1);
    444 
    445  uint32_t colIndex = aCell.ColIndex();
    446  uint32_t rowIndex = aCell.RowIndex();
    447 
    448  if (aCellMap) {
    449    return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex);
    450  }
    451  return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex);
    452 }
    453 
    454 bool nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const {
    455  nsTableCellMap* tableCellMap = GetCellMap();
    456  if (!tableCellMap) ABORT1(1);
    457  return tableCellMap->HasMoreThanOneCell(aRowIndex);
    458 }
    459 
    460 void nsTableFrame::AdjustRowIndices(int32_t aRowIndex, int32_t aAdjustment) {
    461  // Iterate over the row groups and adjust the row indices of all rows
    462  // whose index is >= aRowIndex.
    463  RowGroupArray rowGroups = OrderedRowGroups();
    464 
    465  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
    466    rowGroups[rgIdx]->AdjustRowIndices(aRowIndex, aAdjustment);
    467  }
    468 }
    469 
    470 void nsTableFrame::ResetRowIndices(
    471    const nsFrameList::Slice& aRowGroupsToExclude) {
    472  // Iterate over the row groups and adjust the row indices of all rows
    473  // omit the rowgroups that will be inserted later
    474  mDeletedRowIndexRanges.clear();
    475 
    476  RowGroupArray rowGroups = OrderedRowGroups();
    477 
    478  nsTHashSet<nsTableRowGroupFrame*> excludeRowGroups;
    479  for (nsIFrame* excludeRowGroup : aRowGroupsToExclude) {
    480    excludeRowGroups.Insert(
    481        static_cast<nsTableRowGroupFrame*>(excludeRowGroup));
    482 #ifdef DEBUG
    483    {
    484      // Check to make sure that the row indices of all rows in excluded row
    485      // groups are '0' (i.e. the initial value since they haven't been added
    486      // yet)
    487      const nsFrameList& rowFrames = excludeRowGroup->PrincipalChildList();
    488      for (nsIFrame* r : rowFrames) {
    489        auto* row = static_cast<nsTableRowFrame*>(r);
    490        MOZ_ASSERT(row->GetRowIndex() == 0,
    491                   "exclusions cannot be used for rows that were already added,"
    492                   "because we'd need to process mDeletedRowIndexRanges");
    493      }
    494    }
    495 #endif
    496  }
    497 
    498  int32_t rowIndex = 0;
    499  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
    500    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
    501    if (!excludeRowGroups.Contains(rgFrame)) {
    502      const nsFrameList& rowFrames = rgFrame->PrincipalChildList();
    503      for (nsIFrame* r : rowFrames) {
    504        if (mozilla::StyleDisplay::TableRow == r->StyleDisplay()->mDisplay) {
    505          auto* row = static_cast<nsTableRowFrame*>(r);
    506          row->SetRowIndex(rowIndex);
    507          rowIndex++;
    508        }
    509      }
    510    }
    511  }
    512 }
    513 
    514 void nsTableFrame::InsertColGroups(int32_t aStartColIndex,
    515                                   const nsFrameList::Slice& aColGroups) {
    516  int32_t colIndex = aStartColIndex;
    517 
    518  // XXX: We cannot use range-based for loop because AddColsToTable() can
    519  // destroy the nsTableColGroupFrame in the slice we're traversing! Need to
    520  // check the validity of *colGroupIter.
    521  auto colGroupIter = aColGroups.begin();
    522  for (auto colGroupIterEnd = aColGroups.end();
    523       *colGroupIter && colGroupIter != colGroupIterEnd; ++colGroupIter) {
    524    MOZ_ASSERT((*colGroupIter)->IsTableColGroupFrame());
    525    auto* cgFrame = static_cast<nsTableColGroupFrame*>(*colGroupIter);
    526    cgFrame->SetStartColumnIndex(colIndex);
    527    cgFrame->AddColsToTable(colIndex, false, cgFrame->PrincipalChildList());
    528    int32_t numCols = cgFrame->GetColCount();
    529    colIndex += numCols;
    530  }
    531 
    532  if (*colGroupIter) {
    533    nsTableColGroupFrame::ResetColIndices(*colGroupIter, colIndex);
    534  }
    535 }
    536 
    537 void nsTableFrame::InsertCol(nsTableColFrame& aColFrame, int32_t aColIndex) {
    538  mColFrames.InsertElementAt(aColIndex, &aColFrame);
    539  nsTableColType insertedColType = aColFrame.GetColType();
    540  int32_t numCacheCols = mColFrames.Length();
    541  nsTableCellMap* cellMap = GetCellMap();
    542  if (cellMap) {
    543    int32_t numMapCols = cellMap->GetColCount();
    544    if (numCacheCols > numMapCols) {
    545      bool removedFromCache = false;
    546      if (eColAnonymousCell != insertedColType) {
    547        nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1);
    548        if (lastCol) {
    549          nsTableColType lastColType = lastCol->GetColType();
    550          if (eColAnonymousCell == lastColType) {
    551            // remove the col from the cache
    552            mColFrames.RemoveLastElement();
    553            // remove the col from the synthetic col group
    554            nsTableColGroupFrame* lastColGroup =
    555                (nsTableColGroupFrame*)mColGroups.LastChild();
    556            if (lastColGroup) {
    557              MOZ_ASSERT(lastColGroup->IsSynthetic());
    558              DestroyContext context(PresShell());
    559              lastColGroup->RemoveChild(context, *lastCol, false);
    560 
    561              // remove the col group if it is empty
    562              if (lastColGroup->GetColCount() <= 0) {
    563                mColGroups.DestroyFrame(context, (nsIFrame*)lastColGroup);
    564              }
    565            }
    566            removedFromCache = true;
    567          }
    568        }
    569      }
    570      if (!removedFromCache) {
    571        cellMap->AddColsAtEnd(1);
    572      }
    573    }
    574  }
    575  // for now, just bail and recalc all of the collapsing borders
    576  if (IsBorderCollapse()) {
    577    TableArea damageArea(aColIndex, 0, GetColCount() - aColIndex,
    578                         GetRowCount());
    579    AddBCDamageArea(damageArea);
    580  }
    581 }
    582 
    583 void nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame,
    584                             int32_t aColIndex, bool aRemoveFromCache,
    585                             bool aRemoveFromCellMap) {
    586  if (aRemoveFromCache) {
    587    mColFrames.RemoveElementAt(aColIndex);
    588  }
    589  if (aRemoveFromCellMap) {
    590    nsTableCellMap* cellMap = GetCellMap();
    591    if (cellMap) {
    592      // If we have some anonymous cols at the end already, we just
    593      // add a new anonymous col.
    594      if (!mColFrames.IsEmpty() &&
    595          mColFrames.LastElement() &&  // XXXbz is this ever null?
    596          mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
    597        AppendAnonymousColFrames(1);
    598      } else {
    599        // All of our colframes correspond to actual <col> tags.  It's possible
    600        // that we still have at least as many <col> tags as we have logical
    601        // columns from cells, but we might have one less.  Handle the latter
    602        // case as follows: First ask the cellmap to drop its last col if it
    603        // doesn't have any actual cells in it.  Then call
    604        // MatchCellMapToColCache to append an anonymous column if it's needed;
    605        // this needs to be after RemoveColsAtEnd, since it will determine the
    606        // need for a new column frame based on the width of the cell map.
    607        cellMap->RemoveColsAtEnd();
    608        MatchCellMapToColCache(cellMap);
    609      }
    610    }
    611  }
    612  // for now, just bail and recalc all of the collapsing borders
    613  if (IsBorderCollapse()) {
    614    TableArea damageArea(0, 0, GetColCount(), GetRowCount());
    615    AddBCDamageArea(damageArea);
    616  }
    617 }
    618 
    619 /** Get the cell map for this table frame.  It is not always mCellMap.
    620 * Only the first-in-flow has a legit cell map.
    621 */
    622 nsTableCellMap* nsTableFrame::GetCellMap() const {
    623  return static_cast<nsTableFrame*>(FirstInFlow())->mCellMap.get();
    624 }
    625 
    626 nsTableColGroupFrame* nsTableFrame::CreateSyntheticColGroupFrame() {
    627  nsIContent* colGroupContent = GetContent();
    628  mozilla::PresShell* presShell = PresShell();
    629 
    630  RefPtr<ComputedStyle> colGroupStyle;
    631  colGroupStyle = presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
    632      PseudoStyleType::tableColGroup);
    633  // Create a col group frame
    634  nsTableColGroupFrame* newFrame =
    635      NS_NewTableColGroupFrame(presShell, colGroupStyle);
    636  newFrame->SetIsSynthetic();
    637  newFrame->Init(colGroupContent, this, nullptr);
    638  return newFrame;
    639 }
    640 
    641 void nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) {
    642  MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");
    643  // get the last col group frame
    644  nsTableColGroupFrame* colGroupFrame =
    645      static_cast<nsTableColGroupFrame*>(mColGroups.LastChild());
    646 
    647  if (!colGroupFrame || !colGroupFrame->IsSynthetic()) {
    648    int32_t colIndex = (colGroupFrame) ? colGroupFrame->GetStartColumnIndex() +
    649                                             colGroupFrame->GetColCount()
    650                                       : 0;
    651    colGroupFrame = CreateSyntheticColGroupFrame();
    652    if (!colGroupFrame) {
    653      return;
    654    }
    655    // add the new frame to the child list
    656    mColGroups.AppendFrame(this, colGroupFrame);
    657    colGroupFrame->SetStartColumnIndex(colIndex);
    658  }
    659  AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell,
    660                           true);
    661 }
    662 
    663 // XXX this needs to be moved to nsCSSFrameConstructor
    664 // Right now it only creates the col frames at the end
    665 void nsTableFrame::AppendAnonymousColFrames(
    666    nsTableColGroupFrame* aColGroupFrame, int32_t aNumColsToAdd,
    667    nsTableColType aColType, bool aAddToTable) {
    668  MOZ_ASSERT(aColGroupFrame, "null frame");
    669  MOZ_ASSERT(aColType != eColAnonymousCol, "Shouldn't happen");
    670  MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");
    671 
    672  mozilla::PresShell* presShell = PresShell();
    673 
    674  // Get the last col frame
    675  nsFrameList newColFrames;
    676 
    677  int32_t startIndex = mColFrames.Length();
    678  int32_t lastIndex = startIndex + aNumColsToAdd - 1;
    679 
    680  for (int32_t childX = startIndex; childX <= lastIndex; childX++) {
    681    // all anonymous cols that we create here use a pseudo ComputedStyle of the
    682    // col group
    683    nsIContent* iContent = aColGroupFrame->GetContent();
    684    RefPtr<ComputedStyle> computedStyle =
    685        presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
    686            PseudoStyleType::tableCol);
    687    // ASSERTION to check for bug 54454 sneaking back in...
    688    NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames");
    689 
    690    // create the new col frame
    691    nsIFrame* colFrame = NS_NewTableColFrame(presShell, computedStyle);
    692    ((nsTableColFrame*)colFrame)->SetColType(aColType);
    693    colFrame->Init(iContent, aColGroupFrame, nullptr);
    694 
    695    newColFrames.AppendFrame(nullptr, colFrame);
    696  }
    697  nsFrameList& cols = aColGroupFrame->GetWritableChildList();
    698  nsIFrame* oldLastCol = cols.LastChild();
    699  const nsFrameList::Slice& newCols =
    700      cols.InsertFrames(nullptr, oldLastCol, std::move(newColFrames));
    701  if (aAddToTable) {
    702    // get the starting col index in the cache
    703    int32_t startColIndex;
    704    if (oldLastCol) {
    705      startColIndex =
    706          static_cast<nsTableColFrame*>(oldLastCol)->GetColIndex() + 1;
    707    } else {
    708      startColIndex = aColGroupFrame->GetStartColumnIndex();
    709    }
    710 
    711    aColGroupFrame->AddColsToTable(startColIndex, true, newCols);
    712  }
    713 }
    714 
    715 void nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) {
    716  int32_t numColsInMap = GetColCount();
    717  int32_t numColsInCache = mColFrames.Length();
    718  int32_t numColsToAdd = numColsInMap - numColsInCache;
    719  if (numColsToAdd > 0) {
    720    // this sets the child list, updates the col cache and cell map
    721    AppendAnonymousColFrames(numColsToAdd);
    722  }
    723  if (numColsToAdd < 0) {
    724    int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd);
    725    // if the cell map has fewer cols than the cache, correct it
    726    if (numColsNotRemoved > 0) {
    727      aCellMap->AddColsAtEnd(numColsNotRemoved);
    728    }
    729  }
    730 }
    731 
    732 void nsTableFrame::DidResizeColumns() {
    733  MOZ_ASSERT(!GetPrevInFlow(), "should only be called on first-in-flow");
    734 
    735  if (mBits.mResizedColumns) {
    736    return;  // already marked
    737  }
    738 
    739  for (nsTableFrame* f = this; f;
    740       f = static_cast<nsTableFrame*>(f->GetNextInFlow())) {
    741    f->mBits.mResizedColumns = true;
    742  }
    743 }
    744 
    745 void nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, int32_t aRowIndex) {
    746  nsTableCellMap* cellMap = GetCellMap();
    747  if (cellMap) {
    748    TableArea damageArea(0, 0, 0, 0);
    749    cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea);
    750    MatchCellMapToColCache(cellMap);
    751    if (IsBorderCollapse()) {
    752      AddBCDamageArea(damageArea);
    753    }
    754  }
    755 }
    756 
    757 void nsTableFrame::InsertCells(nsTArray<nsTableCellFrame*>& aCellFrames,
    758                               int32_t aRowIndex, int32_t aColIndexBefore) {
    759  nsTableCellMap* cellMap = GetCellMap();
    760  if (cellMap) {
    761    TableArea damageArea(0, 0, 0, 0);
    762    cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore, damageArea);
    763    MatchCellMapToColCache(cellMap);
    764    if (IsBorderCollapse()) {
    765      AddBCDamageArea(damageArea);
    766    }
    767  }
    768 }
    769 
    770 // this removes the frames from the col group and table, but not the cell map
    771 int32_t nsTableFrame::DestroyAnonymousColFrames(int32_t aNumFrames) {
    772  // only remove cols that are of type eTypeAnonymous cell (they are at the end)
    773  int32_t endIndex = mColFrames.Length() - 1;
    774  int32_t startIndex = (endIndex - aNumFrames) + 1;
    775  int32_t numColsRemoved = 0;
    776  DestroyContext context(PresShell());
    777  for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) {
    778    nsTableColFrame* colFrame = GetColFrame(colIdx);
    779    if (colFrame && (eColAnonymousCell == colFrame->GetColType())) {
    780      auto* cgFrame = static_cast<nsTableColGroupFrame*>(colFrame->GetParent());
    781      // remove the frame from the colgroup
    782      cgFrame->RemoveChild(context, *colFrame, false);
    783      // remove the frame from the cache, but not the cell map
    784      RemoveCol(nullptr, colIdx, true, false);
    785      numColsRemoved++;
    786    } else {
    787      break;
    788    }
    789  }
    790  return (aNumFrames - numColsRemoved);
    791 }
    792 
    793 void nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, int32_t aRowIndex) {
    794  nsTableCellMap* cellMap = GetCellMap();
    795  if (cellMap) {
    796    TableArea damageArea(0, 0, 0, 0);
    797    cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea);
    798    MatchCellMapToColCache(cellMap);
    799    if (IsBorderCollapse()) {
    800      AddBCDamageArea(damageArea);
    801    }
    802  }
    803 }
    804 
    805 int32_t nsTableFrame::GetStartRowIndex(
    806    const nsTableRowGroupFrame* aRowGroupFrame) const {
    807  RowGroupArray orderedRowGroups = OrderedRowGroups();
    808 
    809  int32_t rowIndex = 0;
    810  for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
    811    nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
    812    if (rgFrame == aRowGroupFrame) {
    813      break;
    814    }
    815    int32_t numRows = rgFrame->GetRowCount();
    816    rowIndex += numRows;
    817  }
    818  return rowIndex;
    819 }
    820 
    821 // this cannot extend beyond a single row group
    822 void nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame,
    823                              int32_t aRowIndex,
    824                              nsTArray<nsTableRowFrame*>& aRowFrames) {
    825  nsTableCellMap* cellMap = GetCellMap();
    826  if (cellMap) {
    827    int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex;
    828    InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true);
    829  }
    830 }
    831 
    832 // this cannot extend beyond a single row group
    833 int32_t nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame,
    834                                 nsTArray<nsTableRowFrame*>& aRowFrames,
    835                                 int32_t aRowIndex, bool aConsiderSpans) {
    836 #ifdef DEBUG_TABLE_CELLMAP
    837  printf("=== insertRowsBefore firstRow=%d \n", aRowIndex);
    838  Dump(true, false, true);
    839 #endif
    840 
    841  int32_t numColsToAdd = 0;
    842  nsTableCellMap* cellMap = GetCellMap();
    843  if (cellMap) {
    844    TableArea damageArea(0, 0, 0, 0);
    845    bool shouldRecalculateIndex = !IsDeletedRowIndexRangesEmpty();
    846    if (shouldRecalculateIndex) {
    847      ResetRowIndices(nsFrameList::Slice(nullptr, nullptr));
    848    }
    849    int32_t origNumRows = cellMap->GetRowCount();
    850    int32_t numNewRows = aRowFrames.Length();
    851    cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans,
    852                        damageArea);
    853    MatchCellMapToColCache(cellMap);
    854 
    855    // Perform row index adjustment only if row indices were not
    856    // reset above
    857    if (!shouldRecalculateIndex) {
    858      if (aRowIndex < origNumRows) {
    859        AdjustRowIndices(aRowIndex, numNewRows);
    860      }
    861 
    862      // assign the correct row indices to the new rows. If they were
    863      // recalculated above it may not have been done correctly because each row
    864      // is constructed with index 0
    865      for (int32_t rowB = 0; rowB < numNewRows; rowB++) {
    866        nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowB);
    867        rowFrame->SetRowIndex(aRowIndex + rowB);
    868      }
    869    }
    870 
    871    if (IsBorderCollapse()) {
    872      AddBCDamageArea(damageArea);
    873    }
    874  }
    875 #ifdef DEBUG_TABLE_CELLMAP
    876  printf("=== insertRowsAfter \n");
    877  Dump(true, false, true);
    878 #endif
    879 
    880  return numColsToAdd;
    881 }
    882 
    883 void nsTableFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) {
    884  if (mDeletedRowIndexRanges.empty()) {
    885    mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
    886        aDeletedRowStoredIndex, aDeletedRowStoredIndex));
    887    return;
    888  }
    889 
    890  // Find the position of the current deleted row's stored index
    891  // among the previous deleted row index ranges and merge ranges if
    892  // they are consecutive, else add a new (disjoint) range to the map.
    893  // Call to mDeletedRowIndexRanges.upper_bound is
    894  // O(log(mDeletedRowIndexRanges.size())) therefore call to
    895  // AddDeletedRowIndex is also ~O(log(mDeletedRowIndexRanges.size()))
    896 
    897  // greaterIter = will point to smallest range in the map with lower value
    898  //              greater than the aDeletedRowStoredIndex.
    899  //              If no such value exists, point to end of map.
    900  // smallerIter = will point to largest range in the map with higher value
    901  //              smaller than the aDeletedRowStoredIndex
    902  //              If no such value exists, point to beginning of map.
    903  // i.e. when both values exist below is true:
    904  // smallerIter->second < aDeletedRowStoredIndex < greaterIter->first
    905  auto greaterIter = mDeletedRowIndexRanges.upper_bound(aDeletedRowStoredIndex);
    906  auto smallerIter = greaterIter;
    907 
    908  if (smallerIter != mDeletedRowIndexRanges.begin()) {
    909    smallerIter--;
    910    // While greaterIter might be out-of-bounds (by being equal to end()),
    911    // smallerIter now cannot be, since we returned early above for a 0-size
    912    // map.
    913  }
    914 
    915  // Note: smallerIter can only be equal to greaterIter when both
    916  // of them point to the beginning of the map and in that case smallerIter
    917  // does not "exist" but we clip smallerIter to point to beginning of map
    918  // so that it doesn't point to something unknown or outside the map boundry.
    919  // Note: When greaterIter is not the end (i.e. it "exists") upper_bound()
    920  // ensures aDeletedRowStoredIndex < greaterIter->first so no need to
    921  // assert that.
    922  MOZ_ASSERT(smallerIter == greaterIter ||
    923                 aDeletedRowStoredIndex > smallerIter->second,
    924             "aDeletedRowIndexRanges already contains aDeletedRowStoredIndex! "
    925             "Trying to delete an already deleted row?");
    926 
    927  if (smallerIter->second == aDeletedRowStoredIndex - 1) {
    928    if (greaterIter != mDeletedRowIndexRanges.end() &&
    929        greaterIter->first == aDeletedRowStoredIndex + 1) {
    930      // merge current index with smaller and greater range as they are
    931      // consecutive
    932      smallerIter->second = greaterIter->second;
    933      mDeletedRowIndexRanges.erase(greaterIter);
    934    } else {
    935      // add aDeletedRowStoredIndex in the smaller range as it is consecutive
    936      smallerIter->second = aDeletedRowStoredIndex;
    937    }
    938  } else if (greaterIter != mDeletedRowIndexRanges.end() &&
    939             greaterIter->first == aDeletedRowStoredIndex + 1) {
    940    // add aDeletedRowStoredIndex in the greater range as it is consecutive
    941    mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
    942        aDeletedRowStoredIndex, greaterIter->second));
    943    mDeletedRowIndexRanges.erase(greaterIter);
    944  } else {
    945    // add new range as aDeletedRowStoredIndex is disjoint from existing ranges
    946    mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
    947        aDeletedRowStoredIndex, aDeletedRowStoredIndex));
    948  }
    949 }
    950 
    951 int32_t nsTableFrame::GetAdjustmentForStoredIndex(int32_t aStoredIndex) {
    952  if (mDeletedRowIndexRanges.empty()) {
    953    return 0;
    954  }
    955 
    956  int32_t adjustment = 0;
    957 
    958  // O(log(mDeletedRowIndexRanges.size()))
    959  auto endIter = mDeletedRowIndexRanges.upper_bound(aStoredIndex);
    960  for (auto iter = mDeletedRowIndexRanges.begin(); iter != endIter; ++iter) {
    961    adjustment += iter->second - iter->first + 1;
    962  }
    963 
    964  return adjustment;
    965 }
    966 
    967 // this cannot extend beyond a single row group
    968 void nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame,
    969                              int32_t aNumRowsToRemove, bool aConsiderSpans) {
    970 #ifdef TBD_OPTIMIZATION
    971  // decide if we need to rebalance. we have to do this here because the row
    972  // group cannot do it when it gets the dirty reflow corresponding to the frame
    973  // being destroyed
    974  bool stopTelling = false;
    975  for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking);
    976       kidFrame = kidFrame->GetNextSibling()) {
    977    nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
    978    if (cellFrame) {
    979      stopTelling = tableFrame->CellChangedWidth(
    980          *cellFrame, cellFrame->GetPass1MaxElementWidth(),
    981          cellFrame->GetMaximumWidth(), true);
    982    }
    983  }
    984  // XXX need to consider what happens if there are cells that have rowspans
    985  // into the deleted row. Need to consider moving rows if a rebalance doesn't
    986  // happen
    987 #endif
    988 
    989  int32_t firstRowIndex = aFirstRowFrame.GetRowIndex();
    990 #ifdef DEBUG_TABLE_CELLMAP
    991  printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex,
    992         aNumRowsToRemove);
    993  Dump(true, false, true);
    994 #endif
    995  nsTableCellMap* cellMap = GetCellMap();
    996  if (cellMap) {
    997    TableArea damageArea(0, 0, 0, 0);
    998 
    999    // Mark rows starting from aFirstRowFrame to the next 'aNumRowsToRemove-1'
   1000    // number of rows as deleted.
   1001    nsTableRowGroupFrame* parentFrame = aFirstRowFrame.GetTableRowGroupFrame();
   1002    parentFrame->MarkRowsAsDeleted(aFirstRowFrame, aNumRowsToRemove);
   1003 
   1004    cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans,
   1005                        damageArea);
   1006    MatchCellMapToColCache(cellMap);
   1007    if (IsBorderCollapse()) {
   1008      AddBCDamageArea(damageArea);
   1009    }
   1010  }
   1011 
   1012 #ifdef DEBUG_TABLE_CELLMAP
   1013  printf("=== removeRowsAfter\n");
   1014  Dump(true, true, true);
   1015 #endif
   1016 }
   1017 
   1018 // collect the rows ancestors of aFrame
   1019 int32_t nsTableFrame::CollectRows(nsIFrame* aFrame,
   1020                                  nsTArray<nsTableRowFrame*>& aCollection) {
   1021  MOZ_ASSERT(aFrame, "null frame");
   1022  int32_t numRows = 0;
   1023  for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
   1024    aCollection.AppendElement(static_cast<nsTableRowFrame*>(childFrame));
   1025    numRows++;
   1026  }
   1027  return numRows;
   1028 }
   1029 
   1030 void nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) {
   1031 #ifdef DEBUG_TABLE_CELLMAP
   1032  printf("=== insertRowGroupsBefore\n");
   1033  Dump(true, false, true);
   1034 #endif
   1035  nsTableCellMap* cellMap = GetCellMap();
   1036  if (cellMap) {
   1037    RowGroupArray orderedRowGroups = OrderedRowGroups();
   1038 
   1039    AutoTArray<nsTableRowFrame*, 8> rows;
   1040    // Loop over the rowgroups and check if some of them are new, if they are
   1041    // insert cellmaps in the order that is predefined by OrderedRowGroups.
   1042    // XXXbz this code is O(N*M) where N is number of new rowgroups
   1043    // and M is number of rowgroups we have!
   1044    uint32_t rgIndex;
   1045    for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
   1046      for (nsIFrame* rowGroup : aRowGroups) {
   1047        if (orderedRowGroups[rgIndex] == rowGroup) {
   1048          nsTableRowGroupFrame* priorRG =
   1049              (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
   1050          // create and add the cell map for the row group
   1051          cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG);
   1052 
   1053          break;
   1054        }
   1055      }
   1056    }
   1057    cellMap->Synchronize(this);
   1058    ResetRowIndices(aRowGroups);
   1059 
   1060    // now that the cellmaps are reordered too insert the rows
   1061    for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
   1062      for (nsIFrame* rowGroup : aRowGroups) {
   1063        if (orderedRowGroups[rgIndex] == rowGroup) {
   1064          nsTableRowGroupFrame* priorRG =
   1065              (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
   1066          // collect the new row frames in an array and add them to the table
   1067          int32_t numRows = CollectRows(rowGroup, rows);
   1068          if (numRows > 0) {
   1069            int32_t rowIndex = 0;
   1070            if (priorRG) {
   1071              int32_t priorNumRows = priorRG->GetRowCount();
   1072              rowIndex = priorRG->GetStartRowIndex() + priorNumRows;
   1073            }
   1074            InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true);
   1075            rows.Clear();
   1076          }
   1077          break;
   1078        }
   1079      }
   1080    }
   1081  }
   1082 #ifdef DEBUG_TABLE_CELLMAP
   1083  printf("=== insertRowGroupsAfter\n");
   1084  Dump(true, true, true);
   1085 #endif
   1086 }
   1087 
   1088 /////////////////////////////////////////////////////////////////////////////
   1089 // Child frame enumeration
   1090 
   1091 const nsFrameList& nsTableFrame::GetChildList(ChildListID aListID) const {
   1092  if (aListID == FrameChildListID::ColGroup) {
   1093    return mColGroups;
   1094  }
   1095  return nsContainerFrame::GetChildList(aListID);
   1096 }
   1097 
   1098 void nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
   1099  nsContainerFrame::GetChildLists(aLists);
   1100  mColGroups.AppendIfNonempty(aLists, FrameChildListID::ColGroup);
   1101 }
   1102 
   1103 static inline bool FrameHasBorder(nsIFrame* f) {
   1104  if (!f->StyleVisibility()->IsVisible()) {
   1105    return false;
   1106  }
   1107 
   1108  return f->StyleBorder()->HasBorder();
   1109 }
   1110 
   1111 void nsTableFrame::CalcHasBCBorders() {
   1112  if (!IsBorderCollapse()) {
   1113    SetHasBCBorders(false);
   1114    return;
   1115  }
   1116 
   1117  if (FrameHasBorder(this)) {
   1118    SetHasBCBorders(true);
   1119    return;
   1120  }
   1121 
   1122  // Check col and col group has borders.
   1123  for (nsIFrame* f : this->GetChildList(FrameChildListID::ColGroup)) {
   1124    if (FrameHasBorder(f)) {
   1125      SetHasBCBorders(true);
   1126      return;
   1127    }
   1128 
   1129    nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(f);
   1130    for (nsTableColFrame* col = colGroup->GetFirstColumn(); col;
   1131         col = col->GetNextCol()) {
   1132      if (FrameHasBorder(col)) {
   1133        SetHasBCBorders(true);
   1134        return;
   1135      }
   1136    }
   1137  }
   1138 
   1139  // check row group, row and cell has borders.
   1140  RowGroupArray rowGroups = OrderedRowGroups();
   1141  for (nsTableRowGroupFrame* rowGroup : rowGroups) {
   1142    if (FrameHasBorder(rowGroup)) {
   1143      SetHasBCBorders(true);
   1144      return;
   1145    }
   1146 
   1147    for (nsTableRowFrame* row = rowGroup->GetFirstRow(); row;
   1148         row = row->GetNextRow()) {
   1149      if (FrameHasBorder(row)) {
   1150        SetHasBCBorders(true);
   1151        return;
   1152      }
   1153 
   1154      for (nsTableCellFrame* cell = row->GetFirstCell(); cell;
   1155           cell = cell->GetNextCell()) {
   1156        if (FrameHasBorder(cell)) {
   1157          SetHasBCBorders(true);
   1158          return;
   1159        }
   1160      }
   1161    }
   1162  }
   1163 
   1164  SetHasBCBorders(false);
   1165 }
   1166 
   1167 namespace mozilla {
   1168 class nsDisplayTableBorderCollapse;
   1169 }
   1170 
   1171 // table paint code is concerned primarily with borders and bg color
   1172 // SEC: TODO: adjust the rect for captions
   1173 void nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
   1174                                    const nsDisplayListSet& aLists) {
   1175  DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255, 128, 255));
   1176 
   1177  DisplayBorderBackgroundOutline(aBuilder, aLists);
   1178 
   1179  nsDisplayTableBackgroundSet tableBGs(aBuilder, this);
   1180  nsDisplayListCollection lists(aBuilder);
   1181 
   1182  // This is similar to what
   1183  // nsContainerFrame::BuildDisplayListForNonBlockChildren does, except that we
   1184  // allow the children's background and borders to go in our BorderBackground
   1185  // list. This doesn't really affect background painting --- the children won't
   1186  // actually draw their own backgrounds because the nsTableFrame already drew
   1187  // them, unless a child has its own stacking context, in which case the child
   1188  // won't use its passed-in BorderBackground list anyway. It does affect cell
   1189  // borders though; this lets us get cell borders into the nsTableFrame's
   1190  // BorderBackground list.
   1191  for (nsIFrame* colGroup :
   1192       FirstContinuation()->GetChildList(FrameChildListID::ColGroup)) {
   1193    for (nsIFrame* col : colGroup->PrincipalChildList()) {
   1194      tableBGs.AddColumn((nsTableColFrame*)col);
   1195    }
   1196  }
   1197 
   1198  if (!mFrames.IsEmpty() && !HidesContent()) {
   1199    for (nsIFrame* kid : mFrames) {
   1200      BuildDisplayListForChild(aBuilder, kid, lists);
   1201    }
   1202  }
   1203 
   1204  tableBGs.MoveTo(aLists);
   1205  lists.MoveTo(aLists);
   1206 
   1207  if (IsVisibleForPainting()) {
   1208    // In the collapsed border model, overlay all collapsed borders.
   1209    if (IsBorderCollapse()) {
   1210      if (HasBCBorders()) {
   1211        aLists.BorderBackground()->AppendNewToTop<nsDisplayTableBorderCollapse>(
   1212            aBuilder, this);
   1213      }
   1214    } else {
   1215      const nsStyleBorder* borderStyle = StyleBorder();
   1216      if (borderStyle->HasBorder()) {
   1217        aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder,
   1218                                                                   this);
   1219      }
   1220    }
   1221  }
   1222 }
   1223 
   1224 LogicalSides nsTableFrame::GetLogicalSkipSides() const {
   1225  LogicalSides skip(mWritingMode);
   1226  if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
   1227                   StyleBoxDecorationBreak::Clone)) {
   1228    return skip;
   1229  }
   1230 
   1231  // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto
   1232  // account for pagination
   1233  if (GetPrevInFlow()) {
   1234    skip += LogicalSide::BStart;
   1235  }
   1236  if (GetNextInFlow()) {
   1237    skip += LogicalSide::BEnd;
   1238  }
   1239  return skip;
   1240 }
   1241 
   1242 void nsTableFrame::SetColumnDimensions(nscoord aBSize, WritingMode aWM,
   1243                                       const LogicalMargin& aBorderPadding,
   1244                                       const nsSize& aContainerSize) {
   1245  const nscoord colBSize =
   1246      aBSize - (aBorderPadding.BStartEnd(aWM) + GetRowSpacing(-1) +
   1247                GetRowSpacing(GetRowCount()));
   1248  int32_t colIdx = 0;
   1249  LogicalPoint colGroupOrigin(aWM,
   1250                              aBorderPadding.IStart(aWM) + GetColSpacing(-1),
   1251                              aBorderPadding.BStart(aWM) + GetRowSpacing(-1));
   1252  nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
   1253  for (nsIFrame* colGroupFrame : mColGroups) {
   1254    MOZ_ASSERT(colGroupFrame->IsTableColGroupFrame());
   1255    // first we need to figure out the size of the colgroup
   1256    int32_t groupFirstCol = colIdx;
   1257    nscoord colGroupISize = 0;
   1258    nscoord colSpacing = 0;
   1259    const nsFrameList& columnList = colGroupFrame->PrincipalChildList();
   1260    for (nsIFrame* colFrame : columnList) {
   1261      if (mozilla::StyleDisplay::TableColumn ==
   1262          colFrame->StyleDisplay()->mDisplay) {
   1263        NS_ASSERTION(colIdx < GetColCount(), "invalid number of columns");
   1264        colSpacing = GetColSpacing(colIdx);
   1265        colGroupISize +=
   1266            fif->GetColumnISizeFromFirstInFlow(colIdx) + colSpacing;
   1267        ++colIdx;
   1268      }
   1269    }
   1270    if (colGroupISize) {
   1271      colGroupISize -= colSpacing;
   1272    }
   1273 
   1274    LogicalRect colGroupRect(aWM, colGroupOrigin.I(aWM), colGroupOrigin.B(aWM),
   1275                             colGroupISize, colBSize);
   1276    colGroupFrame->SetRect(aWM, colGroupRect, aContainerSize);
   1277    nsSize colGroupSize = colGroupFrame->GetSize();
   1278 
   1279    // then we can place the columns correctly within the group
   1280    colIdx = groupFirstCol;
   1281    LogicalPoint colOrigin(aWM);
   1282    for (nsIFrame* colFrame : columnList) {
   1283      if (mozilla::StyleDisplay::TableColumn ==
   1284          colFrame->StyleDisplay()->mDisplay) {
   1285        nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
   1286        LogicalRect colRect(aWM, colOrigin.I(aWM), colOrigin.B(aWM), colISize,
   1287                            colBSize);
   1288        colFrame->SetRect(aWM, colRect, colGroupSize);
   1289        colSpacing = GetColSpacing(colIdx);
   1290        colOrigin.I(aWM) += colISize + colSpacing;
   1291        ++colIdx;
   1292      }
   1293    }
   1294 
   1295    colGroupOrigin.I(aWM) += colGroupISize + colSpacing;
   1296  }
   1297 }
   1298 
   1299 // SEC: TODO need to worry about continuing frames prev/next in flow for
   1300 // splitting across pages.
   1301 
   1302 // XXX this could be made more general to handle row modifications that change
   1303 // the table bsize, but first we need to scrutinize every Invalidate
   1304 void nsTableFrame::ProcessRowInserted(nscoord aNewBSize) {
   1305  SetRowInserted(false);  // reset the bit that got us here
   1306  RowGroupArray rowGroups = OrderedRowGroups();
   1307  // find the row group containing the inserted row
   1308  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
   1309    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   1310    NS_ASSERTION(rgFrame, "Must have rgFrame here");
   1311    // find the row that was inserted first
   1312    for (nsIFrame* childFrame : rgFrame->PrincipalChildList()) {
   1313      nsTableRowFrame* rowFrame = do_QueryFrame(childFrame);
   1314      if (rowFrame) {
   1315        if (rowFrame->IsFirstInserted()) {
   1316          rowFrame->SetFirstInserted(false);
   1317          // damage the table from the 1st row inserted to the end of the table
   1318          nsIFrame::InvalidateFrame();
   1319          // XXXbz didn't we do this up front?  Why do we need to do it again?
   1320          SetRowInserted(false);
   1321          return;  // found it, so leave
   1322        }
   1323      }
   1324    }
   1325  }
   1326 }
   1327 
   1328 /* virtual */
   1329 void nsTableFrame::MarkIntrinsicISizesDirty() {
   1330  nsITableLayoutStrategy* tls = LayoutStrategy();
   1331  if (MOZ_UNLIKELY(!tls)) {
   1332    // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame()
   1333    // walking up the ancestor chain in a table next-in-flow.  In this case
   1334    // our original first-in-flow (which owns the TableLayoutStrategy) has
   1335    // already been destroyed and unhooked from the flow chain and thusly
   1336    // LayoutStrategy() returns null.  All the frames in the flow will be
   1337    // destroyed so no need to mark anything dirty here.  See bug 595758.
   1338    return;
   1339  }
   1340  tls->MarkIntrinsicISizesDirty();
   1341 
   1342  // XXXldb Call SetBCDamageArea?
   1343 
   1344  nsContainerFrame::MarkIntrinsicISizesDirty();
   1345 }
   1346 
   1347 nscoord nsTableFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
   1348                                     IntrinsicISizeType aType) {
   1349  if (NeedToCalcBCBorders()) {
   1350    CalcBCBorders();
   1351  }
   1352 
   1353  ReflowColGroups(aInput.mContext);
   1354 
   1355  return aType == IntrinsicISizeType::MinISize
   1356             ? LayoutStrategy()->GetMinISize(aInput.mContext)
   1357             : LayoutStrategy()->GetPrefISize(aInput.mContext, false);
   1358 }
   1359 
   1360 /* virtual */ nsIFrame::IntrinsicSizeOffsetData
   1361 nsTableFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) {
   1362  IntrinsicSizeOffsetData result =
   1363      nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis);
   1364 
   1365  result.margin = 0;
   1366 
   1367  if (IsBorderCollapse()) {
   1368    result.padding = 0;
   1369 
   1370    WritingMode wm = GetWritingMode();
   1371    LogicalMargin outerBC = GetOuterBCBorder(wm);
   1372    result.border = outerBC.IStartEnd(wm);
   1373  }
   1374 
   1375  return result;
   1376 }
   1377 
   1378 /* virtual */
   1379 nsIFrame::SizeComputationResult nsTableFrame::ComputeSize(
   1380    const SizeComputationInput& aSizingInput, WritingMode aWM,
   1381    const LogicalSize& aCBSize, nscoord aAvailableISize,
   1382    const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
   1383    const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
   1384  // Only table wrapper calls this method, and it should use our writing mode.
   1385  MOZ_ASSERT(aWM == GetWritingMode(),
   1386             "aWM should be the same as our writing mode!");
   1387 
   1388  auto result = nsContainerFrame::ComputeSize(
   1389      aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding,
   1390      aSizeOverrides, aFlags);
   1391 
   1392  // If our containing block wants to override inner table frame's inline-size
   1393  // (e.g. when resolving flex base size), don't enforce the min inline-size
   1394  // later in this method.
   1395  if (aSizeOverrides.mApplyOverridesVerbatim && aSizeOverrides.mStyleISize &&
   1396      aSizeOverrides.mStyleISize->IsLengthPercentage()) {
   1397    return result;
   1398  }
   1399 
   1400  // If we're a container for font size inflation, then shrink
   1401  // wrapping inside of us should not apply font size inflation.
   1402  AutoMaybeDisableFontInflation an(this);
   1403 
   1404  // Tables never shrink below their min inline-size.
   1405  const IntrinsicSizeInput input(aSizingInput.mRenderingContext, Some(aCBSize),
   1406                                 Nothing());
   1407  nscoord minISize = GetMinISize(input);
   1408  if (minISize > result.mLogicalSize.ISize(aWM)) {
   1409    result.mLogicalSize.ISize(aWM) = minISize;
   1410  }
   1411 
   1412  return result;
   1413 }
   1414 
   1415 nscoord nsTableFrame::TableShrinkISizeToFit(gfxContext* aRenderingContext,
   1416                                            nscoord aISizeInCB) {
   1417  // If we're a container for font size inflation, then shrink
   1418  // wrapping inside of us should not apply font size inflation.
   1419  AutoMaybeDisableFontInflation an(this);
   1420 
   1421  nscoord result;
   1422  const IntrinsicSizeInput input(aRenderingContext, Nothing(), Nothing());
   1423  nscoord minISize = GetMinISize(input);
   1424  if (minISize > aISizeInCB) {
   1425    result = minISize;
   1426  } else {
   1427    // Tables shrink inline-size to fit with a slightly different algorithm
   1428    // from the one they use for their intrinsic isize (the difference
   1429    // relates to handling of percentage isizes on columns).  So this
   1430    // function differs from nsIFrame::ShrinkISizeToFit by only the
   1431    // following line.
   1432    // Since we've already called GetMinISize, we don't need to do any
   1433    // of the other stuff GetPrefISize does.
   1434    nscoord prefISize = LayoutStrategy()->GetPrefISize(aRenderingContext, true);
   1435    if (prefISize > aISizeInCB) {
   1436      result = aISizeInCB;
   1437    } else {
   1438      result = prefISize;
   1439    }
   1440  }
   1441  return result;
   1442 }
   1443 
   1444 /* virtual */
   1445 LogicalSize nsTableFrame::ComputeAutoSize(
   1446    const SizeComputationInput& aSizingInput, WritingMode aWM,
   1447    const LogicalSize& aCBSize, nscoord aAvailableISize,
   1448    const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
   1449    const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
   1450  // Tables always shrink-wrap.
   1451  nscoord cbBased =
   1452      aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
   1453  return LogicalSize(
   1454      aWM, TableShrinkISizeToFit(aSizingInput.mRenderingContext, cbBased),
   1455      NS_UNCONSTRAINEDSIZE);
   1456 }
   1457 
   1458 // Return true if aParentReflowInput.frame or any of its ancestors within
   1459 // the containing table have non-auto bsize. (e.g. pct or fixed bsize)
   1460 bool nsTableFrame::AncestorsHaveStyleBSize(
   1461    const ReflowInput& aParentReflowInput) {
   1462  WritingMode wm = aParentReflowInput.GetWritingMode();
   1463  for (const ReflowInput* rs = &aParentReflowInput; rs && rs->mFrame;
   1464       rs = rs->mParentReflowInput) {
   1465    LayoutFrameType frameType = rs->mFrame->Type();
   1466    if (LayoutFrameType::TableCell == frameType ||
   1467        LayoutFrameType::TableRow == frameType ||
   1468        LayoutFrameType::TableRowGroup == frameType) {
   1469      const auto bsize =
   1470          rs->mStylePosition->BSize(wm, AnchorPosResolutionParams::From(rs));
   1471      // calc() with both lengths and percentages treated like 'auto' on
   1472      // internal table elements
   1473      if (!bsize->IsAuto() && !bsize->HasLengthAndPercentage()) {
   1474        return true;
   1475      }
   1476    } else if (LayoutFrameType::Table == frameType) {
   1477      // we reached the containing table, so always return
   1478      return !rs->mStylePosition->BSize(wm, AnchorPosResolutionParams::From(rs))
   1479                  ->IsAuto();
   1480    }
   1481  }
   1482  return false;
   1483 }
   1484 
   1485 // See if a special block-size reflow needs to occur and if so,
   1486 // call RequestSpecialBSizeReflow
   1487 void nsTableFrame::CheckRequestSpecialBSizeReflow(
   1488    const ReflowInput& aReflowInput) {
   1489  NS_ASSERTION(aReflowInput.mFrame->IsTableCellFrame() ||
   1490                   aReflowInput.mFrame->IsTableRowFrame() ||
   1491                   aReflowInput.mFrame->IsTableRowGroupFrame() ||
   1492                   aReflowInput.mFrame->IsTableFrame(),
   1493               "unexpected frame type");
   1494  WritingMode wm = aReflowInput.GetWritingMode();
   1495  if (!aReflowInput.mFrame->GetPrevInFlow() &&  // 1st in flow
   1496      (NS_UNCONSTRAINEDSIZE ==
   1497           aReflowInput.ComputedBSize() ||  // no computed bsize
   1498       0 == aReflowInput.ComputedBSize()) &&
   1499      aReflowInput.mStylePosition
   1500          ->BSize(wm, AnchorPosResolutionParams::From(&aReflowInput))
   1501          ->ConvertsToPercentage() &&  // pct bsize
   1502      nsTableFrame::AncestorsHaveStyleBSize(*aReflowInput.mParentReflowInput)) {
   1503    nsTableFrame::RequestSpecialBSizeReflow(aReflowInput);
   1504  }
   1505 }
   1506 
   1507 // Notify the frame and its ancestors (up to the containing table) that a
   1508 // special bsize reflow will occur. During a special bsize reflow, a table, row
   1509 // group, row, or cell returns the last size it was reflowed at. However, the
   1510 // table may change the bsize of row groups, rows, cells in
   1511 // DistributeBSizeToRows after. And the row group can change the bsize of rows,
   1512 // cells in CalculateRowBSizes.
   1513 void nsTableFrame::RequestSpecialBSizeReflow(const ReflowInput& aReflowInput) {
   1514  // notify the frame and its ancestors of the special reflow, stopping at the
   1515  // containing table
   1516  for (const ReflowInput* rs = &aReflowInput; rs && rs->mFrame;
   1517       rs = rs->mParentReflowInput) {
   1518    LayoutFrameType frameType = rs->mFrame->Type();
   1519    NS_ASSERTION(LayoutFrameType::TableCell == frameType ||
   1520                     LayoutFrameType::TableRow == frameType ||
   1521                     LayoutFrameType::TableRowGroup == frameType ||
   1522                     LayoutFrameType::Table == frameType,
   1523                 "unexpected frame type");
   1524 
   1525    rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
   1526    if (LayoutFrameType::Table == frameType) {
   1527      NS_ASSERTION(rs != &aReflowInput,
   1528                   "should not request special bsize reflow for table");
   1529      // always stop when we reach a table
   1530      break;
   1531    }
   1532  }
   1533 }
   1534 
   1535 /******************************************************************************************
   1536 * Before reflow, intrinsic inline-size calculation is done using GetMinISize
   1537 * and GetPrefISize.  This used to be known as pass 1 reflow.
   1538 *
   1539 * After the intrinsic isize calculation, the table determines the
   1540 * column widths using BalanceColumnISizes() and
   1541 * then reflows each child again with a constrained avail isize. This reflow is
   1542 * referred to as the pass 2 reflow.
   1543 *
   1544 * A special bsize reflow (pass 3 reflow) can occur during an initial or resize
   1545 * reflow if (a) a row group, row, cell, or a frame inside a cell has a percent
   1546 * bsize but no computed bsize or (b) in paginated mode, a table has a bsize.
   1547 * (a) supports percent nested tables contained inside cells whose bsizes aren't
   1548 * known until after the pass 2 reflow. (b) is necessary because the table
   1549 * cannot split until after the pass 2 reflow. The mechanics of the special
   1550 * bsize reflow (variety a) are as follows:
   1551 *
   1552 * 1) Each table related frame (table, row group, row, cell) implements
   1553 *    NeedsSpecialReflow() to indicate that it should get the reflow. It does
   1554 *    this when it has a percent bsize but no computed bsize by calling
   1555 *    CheckRequestSpecialBSizeReflow(). This method calls
   1556 *    RequestSpecialBSizeReflow() which calls SetNeedSpecialReflow() on its
   1557 *    ancestors until it reaches the containing table and calls
   1558 *    SetNeedToInitiateSpecialReflow() on it. For percent bsize frames inside
   1559 *    cells, during DidReflow(), the cell's NotifyPercentBSize() is called
   1560 *    (the cell is the reflow input's mPercentBSizeObserver in this case).
   1561 *    NotifyPercentBSize() calls RequestSpecialBSizeReflow().
   1562 *
   1563 * XXX (jfkthame) This comment appears to be out of date; it refers to
   1564 * methods/flags that are no longer present in the code.
   1565 *
   1566 * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true)
   1567 *    was called, it will do the special bsize reflow, setting the reflow
   1568 *    input's mFlags.mSpecialBSizeReflow to true and mSpecialHeightInitiator to
   1569 *    itself. It won't do this if IsPrematureSpecialHeightReflow() returns true
   1570 *    because in that case another special bsize reflow will be coming along
   1571 *    with the containing table as the mSpecialHeightInitiator. It is only
   1572 *    relevant to do the reflow when the mSpecialHeightInitiator is the
   1573 *    containing table, because if it is a remote ancestor, then appropriate
   1574 *    bsizes will not be known.
   1575 *
   1576 * 3) Since the bsizes of the table, row groups, rows, and cells was determined
   1577 *    during the pass 2 reflow, they return their last desired sizes during the
   1578 *    special bsize reflow. The reflow only permits percent bsize frames inside
   1579 *    the cells to resize based on the cells bsize and that bsize was
   1580 *    determined during the pass 2 reflow.
   1581 *
   1582 * So, in the case of deeply nested tables, all of the tables that were told to
   1583 * initiate a special reflow will do so, but if a table is already in a special
   1584 * reflow, it won't inititate the reflow until the current initiator is its
   1585 * containing table. Since these reflows are only received by frames that need
   1586 * them and they don't cause any rebalancing of tables, the extra overhead is
   1587 * minimal.
   1588 *
   1589 * The type of special reflow that occurs during printing (variety b) follows
   1590 * the same mechanism except that all frames will receive the reflow even if
   1591 * they don't really need them.
   1592 *
   1593 * Open issues with the special bsize reflow:
   1594 *
   1595 * 1) At some point there should be 2 kinds of special bsize reflows because (a)
   1596 *    and (b) above are really quite different. This would avoid unnecessary
   1597 *    reflows during printing.
   1598 *
   1599 * 2) When a cell contains frames whose percent bsizes > 100%, there is data
   1600 *    loss (see bug 115245). However, this can also occur if a cell has a fixed
   1601 *    bsize and there is no special bsize reflow.
   1602 *
   1603 * XXXldb Special bsize reflow should really be its own method, not
   1604 * part of nsIFrame::Reflow.  It should then call nsIFrame::Reflow on
   1605 * the contents of the cells to do the necessary block-axis resizing.
   1606 *
   1607 ******************************************************************************************/
   1608 
   1609 /* Layout the entire inner table. */
   1610 void nsTableFrame::Reflow(nsPresContext* aPresContext,
   1611                          ReflowOutput& aDesiredSize,
   1612                          const ReflowInput& aReflowInput,
   1613                          nsReflowStatus& aStatus) {
   1614  MarkInReflow();
   1615  DO_GLOBAL_REFLOW_COUNT("nsTableFrame");
   1616  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
   1617  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
   1618             "The nsTableWrapperFrame should be the out-of-flow if needed");
   1619 
   1620  const WritingMode wm = aReflowInput.GetWritingMode();
   1621  MOZ_ASSERT(aReflowInput.ComputedLogicalMargin(wm).IsAllZero(),
   1622             "Only nsTableWrapperFrame can have margins!");
   1623 
   1624  bool isPaginated = aPresContext->IsPaginated();
   1625 
   1626  if (!GetPrevInFlow() && !mTableLayoutStrategy) {
   1627    NS_ERROR("strategy should have been created in Init");
   1628    return;
   1629  }
   1630 
   1631  // see if collapsing borders need to be calculated
   1632  if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) {
   1633    CalcBCBorders();
   1634  }
   1635 
   1636  // Check for an overflow list, and append any row group frames being pushed
   1637  MoveOverflowToChildList();
   1638 
   1639  bool haveCalledCalcDesiredBSize = false;
   1640  SetHaveReflowedColGroups(false);
   1641 
   1642  LogicalMargin borderPadding =
   1643      aReflowInput.ComputedLogicalBorderPadding(wm).ApplySkipSides(
   1644          PreReflowBlockLevelLogicalSkipSides());
   1645  nsIFrame* lastChildReflowed = nullptr;
   1646  const nsSize containerSize =
   1647      aReflowInput.ComputedSizeAsContainerIfConstrained();
   1648 
   1649  // The tentative width is the width we assumed for the table when the child
   1650  // frames were positioned (which only matters in vertical-rl mode, because
   1651  // they're positioned relative to the right-hand edge). Then, after reflowing
   1652  // the kids, we can check whether the table ends up with a different width
   1653  // than this tentative value (either because it was unconstrained, so we used
   1654  // zero, or because it was enlarged by the child frames), we make the
   1655  // necessary positioning adjustments along the x-axis.
   1656  nscoord tentativeContainerWidth = 0;
   1657  bool mayAdjustXForAllChildren = false;
   1658 
   1659  // Reflow the entire table (pass 2 and possibly pass 3). This phase is
   1660  // necessary during a constrained initial reflow and other reflows which
   1661  // require either a strategy init or balance. This isn't done during an
   1662  // unconstrained reflow, because it will occur later when the parent reflows
   1663  // with a constrained isize.
   1664  if (IsSubtreeDirty() || aReflowInput.ShouldReflowAllKids() ||
   1665      IsGeometryDirty() || isPaginated || aReflowInput.IsBResize() ||
   1666      NeedToCollapse()) {
   1667    if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
   1668        // Also check IsBResize(), to handle the first Reflow preceding a
   1669        // special bsize Reflow, when we've already had a special bsize
   1670        // Reflow (where ComputedBSize() would not be
   1671        // NS_UNCONSTRAINEDSIZE, but without a style change in between).
   1672        aReflowInput.IsBResize()) {
   1673      // XXX Eventually, we should modify DistributeBSizeToRows to use
   1674      // nsTableRowFrame::GetInitialBSize instead of nsIFrame::BSize().
   1675      // That way, it will make its calculations based on internal table
   1676      // frame bsizes as they are before they ever had any extra bsize
   1677      // distributed to them.  In the meantime, this reflows all the
   1678      // internal table frames, which restores them to their state before
   1679      // DistributeBSizeToRows was called.
   1680      SetGeometryDirty();
   1681    }
   1682 
   1683    bool needToInitiateSpecialReflow = false;
   1684    if (isPaginated) {
   1685      // see if an extra reflow will be necessary in pagination mode
   1686      // when there is a specified table bsize
   1687      if (!GetPrevInFlow() &&
   1688          NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize()) {
   1689        nscoord tableSpecifiedBSize = CalcBorderBoxBSize(
   1690            aReflowInput, borderPadding, NS_UNCONSTRAINEDSIZE);
   1691        if (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE &&
   1692            tableSpecifiedBSize > 0) {
   1693          needToInitiateSpecialReflow = true;
   1694        }
   1695      }
   1696    } else {
   1697      needToInitiateSpecialReflow =
   1698          HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
   1699    }
   1700 
   1701    NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow,
   1702                 "Shouldn't be in special bsize reflow here!");
   1703 
   1704    const TableReflowMode firstReflowMode = needToInitiateSpecialReflow
   1705                                                ? TableReflowMode::Measuring
   1706                                                : TableReflowMode::Final;
   1707    ReflowTable(aDesiredSize, aReflowInput, borderPadding, firstReflowMode,
   1708                lastChildReflowed, aStatus);
   1709 
   1710    // When in vertical-rl mode, there may be two kinds of scenarios in which
   1711    // the positioning of all the children need to be adjusted along the x-axis
   1712    // because the width we assumed for the table when the child frames were
   1713    // being positioned(i.e. tentative width) may be different from the final
   1714    // width for the table:
   1715    // 1. If the computed width for the table is unconstrained, a dummy zero
   1716    //    width was assumed as the tentative width to begin with.
   1717    // 2. If the child frames enlarge the width for the table, the final width
   1718    //    becomes larger than the tentative one.
   1719    // Let's record the tentative width here, if later the final width turns out
   1720    // to be different from this tentative one, it means one of the above
   1721    // scenarios happens, then we adjust positioning of all the children.
   1722    // Note that vertical-lr, unlike vertical-rl, doesn't need to take special
   1723    // care of this situation, because they're positioned relative to the
   1724    // left-hand edge.
   1725    if (wm.IsVerticalRL()) {
   1726      tentativeContainerWidth = containerSize.width;
   1727      mayAdjustXForAllChildren = true;
   1728    }
   1729 
   1730    // reevaluate special bsize reflow conditions
   1731    if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
   1732      needToInitiateSpecialReflow = true;
   1733    }
   1734 
   1735    // XXXldb Are all these conditions correct?
   1736    if (needToInitiateSpecialReflow && aStatus.IsComplete()) {
   1737      // XXXldb Do we need to set the IsBResize flag on any reflow inputs?
   1738 
   1739      ReflowInput& mutable_rs = const_cast<ReflowInput&>(aReflowInput);
   1740 
   1741      // distribute extra block-direction space to rows
   1742      aDesiredSize.BSize(wm) =
   1743          CalcDesiredBSize(aReflowInput, borderPadding, aStatus);
   1744      haveCalledCalcDesiredBSize = true;
   1745 
   1746      mutable_rs.mFlags.mSpecialBSizeReflow = true;
   1747 
   1748      ReflowTable(aDesiredSize, aReflowInput, borderPadding,
   1749                  TableReflowMode::Final, lastChildReflowed, aStatus);
   1750 
   1751      mutable_rs.mFlags.mSpecialBSizeReflow = false;
   1752    }
   1753  }
   1754 
   1755  if (aStatus.IsIncomplete() &&
   1756      aReflowInput.mStyleBorder->mBoxDecorationBreak ==
   1757          StyleBoxDecorationBreak::Slice) {
   1758    borderPadding.BEnd(wm) = 0;
   1759  }
   1760 
   1761  aDesiredSize.ISize(wm) =
   1762      aReflowInput.ComputedISize() + borderPadding.IStartEnd(wm);
   1763  if (!haveCalledCalcDesiredBSize) {
   1764    aDesiredSize.BSize(wm) =
   1765        CalcDesiredBSize(aReflowInput, borderPadding, aStatus);
   1766  } else if (lastChildReflowed && aStatus.IsIncomplete()) {
   1767    // If there is an incomplete child, then set the desired block-size to
   1768    // include it but not the next one.
   1769    aDesiredSize.BSize(wm) =
   1770        borderPadding.BEnd(wm) +
   1771        lastChildReflowed->GetLogicalNormalRect(wm, containerSize).BEnd(wm);
   1772  }
   1773  if (IsRowInserted()) {
   1774    ProcessRowInserted(aDesiredSize.BSize(wm));
   1775  }
   1776 
   1777  // For more information on the reason for what we should do this, refer to the
   1778  // code which defines and evaluates the variables xAdjustmentForAllKids and
   1779  // tentativeContainerWidth in the previous part in this function.
   1780  if (mayAdjustXForAllChildren) {
   1781    nscoord xAdjustmentForAllKids =
   1782        aDesiredSize.Width() - tentativeContainerWidth;
   1783    if (0 != xAdjustmentForAllKids) {
   1784      for (nsIFrame* kid : mFrames) {
   1785        kid->MovePositionBy(nsPoint(xAdjustmentForAllKids, 0));
   1786      }
   1787    }
   1788  }
   1789 
   1790  // Calculate the overflow area contribution from our children. We couldn't
   1791  // do this on the fly during ReflowChildren(), because in vertical-rl mode
   1792  // with unconstrained width, we weren't placing them in their final positions
   1793  // until the fixupKidPositions loop just above.
   1794  for (nsIFrame* kid : mFrames) {
   1795    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid);
   1796  }
   1797 
   1798  SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding,
   1799                      aDesiredSize.PhysicalSize());
   1800  NS_WARNING_ASSERTION(NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(),
   1801                       "reflow branch removed unconstrained available isizes");
   1802  if (NeedToCollapse()) {
   1803    // This code and the code it depends on assumes that all row groups
   1804    // and rows have just been reflowed (i.e., it makes adjustments to
   1805    // their rects that are not idempotent).  Thus the reflow code
   1806    // checks NeedToCollapse() to ensure this is true.
   1807    AdjustForCollapsingRowsCols(aDesiredSize, wm, borderPadding);
   1808  }
   1809 
   1810  // If there are any relatively-positioned table parts, we need to reflow their
   1811  // absolutely-positioned descendants now that their dimensions are final.
   1812  FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowInput);
   1813 
   1814  // make sure the table overflow area does include the table rect.
   1815  nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height());
   1816 
   1817  aDesiredSize.mOverflowAreas.UnionAllWith(tableRect);
   1818 
   1819  FinishAndStoreOverflow(&aDesiredSize);
   1820 }
   1821 
   1822 void nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext,
   1823                                             ReflowOutput& aDesiredSize,
   1824                                             const ReflowInput& aReflowInput) {
   1825  TablePartsArray* positionedParts =
   1826      GetProperty(PositionedTablePartsProperty());
   1827  if (!positionedParts) {
   1828    return;
   1829  }
   1830 
   1831  OverflowChangedTracker overflowTracker;
   1832  overflowTracker.SetSubtreeRoot(this);
   1833 
   1834  for (nsContainerFrame* positionedPart : *positionedParts) {
   1835    // As we've already finished reflow, positionedParts's size and overflow
   1836    // areas have already been assigned, so we just pull them back out.
   1837    const WritingMode wm = positionedPart->GetWritingMode();
   1838    const LogicalSize size = positionedPart->GetLogicalSize(wm);
   1839    ReflowOutput desiredSize(aReflowInput.GetWritingMode());
   1840    desiredSize.SetSize(wm, size);
   1841    desiredSize.mOverflowAreas =
   1842        positionedPart->GetOverflowAreasRelativeToSelf();
   1843 
   1844    // Construct a dummy reflow input and reflow status.
   1845    // XXX(seth): Note that the dummy reflow input doesn't have a correct
   1846    // chain of parent reflow inputs. It also doesn't necessarily have a
   1847    // correct containing block.
   1848    LogicalSize availSize = size;
   1849    availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
   1850    ReflowInput reflowInput(aPresContext, positionedPart,
   1851                            aReflowInput.mRenderingContext, availSize,
   1852                            ReflowInput::InitFlag::DummyParentReflowInput);
   1853    nsReflowStatus reflowStatus;
   1854 
   1855    // Reflow absolutely-positioned descendants of the positioned part.
   1856    // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the bsize and
   1857    // ignoring any change to the reflow status aren't correct. We'll never
   1858    // paginate absolutely positioned frames.
   1859    positionedPart->FinishReflowWithAbsoluteFrames(PresContext(), desiredSize,
   1860                                                   reflowInput, reflowStatus);
   1861 
   1862    // FinishReflowWithAbsoluteFrames has updated overflow on
   1863    // |positionedPart|.  We need to make sure that update propagates
   1864    // through the intermediate frames between it and this frame.
   1865    nsIFrame* positionedFrameParent = positionedPart->GetParent();
   1866    if (positionedFrameParent != this) {
   1867      overflowTracker.AddFrame(positionedFrameParent,
   1868                               OverflowChangedTracker::CHILDREN_CHANGED);
   1869    }
   1870  }
   1871 
   1872  // Propagate updated overflow areas up the tree.
   1873  overflowTracker.Flush();
   1874 
   1875  // Update our own overflow areas. (OverflowChangedTracker doesn't update the
   1876  // subtree root itself.)
   1877  aDesiredSize.SetOverflowAreasToDesiredBounds();
   1878  nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas);
   1879 }
   1880 
   1881 bool nsTableFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
   1882  return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
   1883 }
   1884 
   1885 void nsTableFrame::ReflowTable(ReflowOutput& aDesiredSize,
   1886                               const ReflowInput& aReflowInput,
   1887                               const LogicalMargin& aBorderPadding,
   1888                               TableReflowMode aReflowMode,
   1889                               nsIFrame*& aLastChildReflowed,
   1890                               nsReflowStatus& aStatus) {
   1891  aLastChildReflowed = nullptr;
   1892 
   1893  if (!GetPrevInFlow()) {
   1894    mTableLayoutStrategy->ComputeColumnISizes(aReflowInput);
   1895  }
   1896 
   1897  TableReflowInput reflowInput(aReflowInput, aBorderPadding, aReflowMode);
   1898  ReflowChildren(reflowInput, aStatus, aLastChildReflowed,
   1899                 aDesiredSize.mOverflowAreas);
   1900 
   1901  ReflowColGroups(aReflowInput.mRenderingContext);
   1902 }
   1903 
   1904 void nsTableFrame::PushChildrenToOverflow(const RowGroupArray& aRowGroups,
   1905                                          size_t aPushFrom) {
   1906  MOZ_ASSERT(aPushFrom > 0, "pushing first child");
   1907 
   1908  // Extract the frames from the array into a frame list.
   1909  nsFrameList frames;
   1910  for (size_t childX = aPushFrom; childX < aRowGroups.Length(); ++childX) {
   1911    nsTableRowGroupFrame* rgFrame = aRowGroups[childX];
   1912    if (!rgFrame->IsRepeatable()) {
   1913      mFrames.RemoveFrame(rgFrame);
   1914      frames.AppendFrame(nullptr, rgFrame);
   1915    }
   1916  }
   1917 
   1918  if (frames.IsEmpty()) {
   1919    return;
   1920  }
   1921 
   1922  // Add the frames to our overflow list.
   1923  SetOverflowFrames(std::move(frames));
   1924 }
   1925 
   1926 // collapsing row groups, rows, col groups and cols are accounted for after both
   1927 // passes of reflow so that it has no effect on the calculations of reflow.
   1928 void nsTableFrame::AdjustForCollapsingRowsCols(
   1929    ReflowOutput& aDesiredSize, const WritingMode aWM,
   1930    const LogicalMargin& aBorderPadding) {
   1931  nscoord bTotalOffset = 0;  // total offset among all rows in all row groups
   1932 
   1933  // reset the bit, it will be set again if row/rowgroup or col/colgroup are
   1934  // collapsed
   1935  SetNeedToCollapse(false);
   1936 
   1937  // collapse the rows and/or row groups as necessary
   1938  // Get the ordered children
   1939  RowGroupArray rowGroups = OrderedRowGroups();
   1940 
   1941  nsTableFrame* firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
   1942  nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding);
   1943  nscoord rgISize = iSize - GetColSpacing(-1) - GetColSpacing(GetColCount());
   1944  OverflowAreas overflow;
   1945  // Walk the list of children
   1946  for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) {
   1947    nsTableRowGroupFrame* rgFrame = rowGroups[childX];
   1948    NS_ASSERTION(rgFrame, "Must have row group frame here");
   1949    bTotalOffset +=
   1950        rgFrame->CollapseRowGroupIfNecessary(bTotalOffset, rgISize, aWM);
   1951    ConsiderChildOverflow(overflow, rgFrame);
   1952  }
   1953 
   1954  aDesiredSize.BSize(aWM) -= bTotalOffset;
   1955  aDesiredSize.ISize(aWM) = iSize;
   1956  overflow.UnionAllWith(
   1957      nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()));
   1958  FinishAndStoreOverflow(overflow,
   1959                         nsSize(aDesiredSize.Width(), aDesiredSize.Height()));
   1960 }
   1961 
   1962 nscoord nsTableFrame::GetCollapsedISize(const WritingMode aWM,
   1963                                        const LogicalMargin& aBorderPadding) {
   1964  NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedISize called on next in flow");
   1965  nscoord iSize = GetColSpacing(GetColCount());
   1966  iSize += aBorderPadding.IStartEnd(aWM);
   1967  nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
   1968  for (nsIFrame* groupFrame : mColGroups) {
   1969    const nsStyleVisibility* groupVis = groupFrame->StyleVisibility();
   1970    bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
   1971    nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame;
   1972    for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame;
   1973         colFrame = colFrame->GetNextCol()) {
   1974      const nsStyleDisplay* colDisplay = colFrame->StyleDisplay();
   1975      nscoord colIdx = colFrame->GetColIndex();
   1976      if (mozilla::StyleDisplay::TableColumn == colDisplay->mDisplay) {
   1977        const nsStyleVisibility* colVis = colFrame->StyleVisibility();
   1978        bool collapseCol = StyleVisibility::Collapse == colVis->mVisible;
   1979        nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
   1980        if (!collapseGroup && !collapseCol) {
   1981          iSize += colISize;
   1982          if (ColumnHasCellSpacingBefore(colIdx)) {
   1983            iSize += GetColSpacing(colIdx - 1);
   1984          }
   1985        } else {
   1986          SetNeedToCollapse(true);
   1987        }
   1988      }
   1989    }
   1990  }
   1991  return iSize;
   1992 }
   1993 
   1994 /* virtual */
   1995 void nsTableFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
   1996  nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
   1997 
   1998  if (!aOldComputedStyle) {  // avoid this on init
   1999    return;
   2000  }
   2001 
   2002  if (IsBorderCollapse() && BCRecalcNeeded(aOldComputedStyle, Style())) {
   2003    SetFullBCDamageArea();
   2004  }
   2005 
   2006  // avoid this on init or nextinflow
   2007  if (!mTableLayoutStrategy || GetPrevInFlow()) {
   2008    return;
   2009  }
   2010 
   2011  bool isAuto = IsAutoLayout();
   2012  if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) {
   2013    if (isAuto) {
   2014      mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this);
   2015    } else {
   2016      mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this);
   2017    }
   2018  }
   2019 }
   2020 
   2021 void nsTableFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
   2022  NS_ASSERTION(aListID == FrameChildListID::Principal ||
   2023                   aListID == FrameChildListID::ColGroup,
   2024               "unexpected child list");
   2025 
   2026  // Because we actually have two child lists, one for col group frames and one
   2027  // for everything else, we need to look at each frame individually
   2028  // XXX The frame construction code should be separating out child frames
   2029  // based on the type, bug 343048.
   2030  while (!aFrameList.IsEmpty()) {
   2031    nsIFrame* f = aFrameList.FirstChild();
   2032    aFrameList.RemoveFrame(f);
   2033 
   2034    // See what kind of frame we have
   2035    const nsStyleDisplay* display = f->StyleDisplay();
   2036 
   2037    if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
   2038      if (MOZ_UNLIKELY(GetPrevInFlow())) {
   2039        nsFrameList colgroupFrame(f, f);
   2040        auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
   2041        firstInFlow->AppendFrames(aListID, std::move(colgroupFrame));
   2042        continue;
   2043      }
   2044      nsTableColGroupFrame* lastColGroup =
   2045          nsTableColGroupFrame::GetLastRealColGroup(this);
   2046      int32_t startColIndex = (lastColGroup)
   2047                                  ? lastColGroup->GetStartColumnIndex() +
   2048                                        lastColGroup->GetColCount()
   2049                                  : 0;
   2050      mColGroups.InsertFrame(this, lastColGroup, f);
   2051      // Insert the colgroup and its cols into the table
   2052      InsertColGroups(startColIndex,
   2053                      nsFrameList::Slice(f, f->GetNextSibling()));
   2054    } else if (IsRowGroup(display->mDisplay)) {
   2055      DrainSelfOverflowList();  // ensure the last frame is in mFrames
   2056      // Append the new row group frame to the sibling chain
   2057      mFrames.AppendFrame(nullptr, f);
   2058 
   2059      // insert the row group and its rows into the table
   2060      InsertRowGroups(nsFrameList::Slice(f, nullptr));
   2061    } else {
   2062      // Nothing special to do, just add the frame to our child list
   2063      MOZ_ASSERT_UNREACHABLE(
   2064          "How did we get here? Frame construction screwed up");
   2065      mFrames.AppendFrame(nullptr, f);
   2066    }
   2067  }
   2068 
   2069 #ifdef DEBUG_TABLE_CELLMAP
   2070  printf("=== TableFrame::AppendFrames\n");
   2071  Dump(true, true, true);
   2072 #endif
   2073  PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
   2074                                NS_FRAME_HAS_DIRTY_CHILDREN);
   2075  SetGeometryDirty();
   2076 }
   2077 
   2078 void nsTableFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
   2079                                const nsLineList::iterator* aPrevFrameLine,
   2080                                nsFrameList&& aFrameList) {
   2081  // The frames in aFrameList can be a mix of row group frames and col group
   2082  // frames. The problem is that they should go in separate child lists so
   2083  // we need to deal with that here...
   2084  // XXX The frame construction code should be separating out child frames
   2085  // based on the type, bug 343048.
   2086 
   2087  NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
   2088               "inserting after sibling frame with different parent");
   2089 
   2090  if ((aPrevFrame && !aPrevFrame->GetNextSibling()) ||
   2091      (!aPrevFrame && GetChildList(aListID).IsEmpty())) {
   2092    // Treat this like an append; still a workaround for bug 343048.
   2093    AppendFrames(aListID, std::move(aFrameList));
   2094    return;
   2095  }
   2096 
   2097  // Collect ColGroupFrames into a separate list and insert those separately
   2098  // from the other frames (bug 759249).
   2099  nsFrameList colGroupList;
   2100  nsFrameList principalList;
   2101  do {
   2102    const auto display = aFrameList.FirstChild()->StyleDisplay()->mDisplay;
   2103    nsFrameList head = aFrameList.Split([display](nsIFrame* aFrame) {
   2104      return aFrame->StyleDisplay()->mDisplay != display;
   2105    });
   2106    if (display == mozilla::StyleDisplay::TableColumnGroup) {
   2107      colGroupList.AppendFrames(nullptr, std::move(head));
   2108    } else {
   2109      principalList.AppendFrames(nullptr, std::move(head));
   2110    }
   2111  } while (aFrameList.NotEmpty());
   2112 
   2113  // We pass aPrevFrame for both ColGroup and other frames since
   2114  // HomogenousInsertFrames will only use it if it's a suitable
   2115  // prev-sibling for the frames in the frame list.
   2116  if (colGroupList.NotEmpty()) {
   2117    HomogenousInsertFrames(FrameChildListID::ColGroup, aPrevFrame,
   2118                           colGroupList);
   2119  }
   2120  if (principalList.NotEmpty()) {
   2121    HomogenousInsertFrames(FrameChildListID::Principal, aPrevFrame,
   2122                           principalList);
   2123  }
   2124 }
   2125 
   2126 void nsTableFrame::HomogenousInsertFrames(ChildListID aListID,
   2127                                          nsIFrame* aPrevFrame,
   2128                                          nsFrameList& aFrameList) {
   2129  // See what kind of frame we have
   2130  const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay();
   2131  bool isColGroup =
   2132      mozilla::StyleDisplay::TableColumnGroup == display->mDisplay;
   2133 #ifdef DEBUG
   2134  // Verify that either all siblings have display:table-column-group, or they
   2135  // all have display values different from table-column-group.
   2136  for (nsIFrame* frame : aFrameList) {
   2137    auto nextDisplay = frame->StyleDisplay()->mDisplay;
   2138    MOZ_ASSERT(
   2139        isColGroup == (nextDisplay == mozilla::StyleDisplay::TableColumnGroup),
   2140        "heterogenous childlist");
   2141  }
   2142 #endif
   2143  if (MOZ_UNLIKELY(isColGroup && GetPrevInFlow())) {
   2144    auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
   2145    firstInFlow->AppendFrames(aListID, std::move(aFrameList));
   2146    return;
   2147  }
   2148  if (aPrevFrame) {
   2149    const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay();
   2150    // Make sure they belong on the same frame list
   2151    if ((display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) !=
   2152        (prevDisplay->mDisplay == mozilla::StyleDisplay::TableColumnGroup)) {
   2153      // the previous frame is not valid, see comment at ::AppendFrames
   2154      // XXXbz Using content indices here means XBL will get screwed
   2155      // over...  Oh, well.
   2156      nsIFrame* pseudoFrame = aFrameList.FirstChild();
   2157      nsIContent* parentContent = GetContent();
   2158      nsIContent* content = nullptr;
   2159      aPrevFrame = nullptr;
   2160      while (pseudoFrame &&
   2161             (parentContent == (content = pseudoFrame->GetContent()))) {
   2162        pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
   2163      }
   2164      nsCOMPtr<nsIContent> container = content->GetParent();
   2165      if (MOZ_LIKELY(container)) {  // XXX need this null-check, see bug 411823.
   2166        const Maybe<uint32_t> newIndex = container->ComputeIndexOf(content);
   2167        nsIFrame* kidFrame;
   2168        nsTableColGroupFrame* lastColGroup = nullptr;
   2169        if (isColGroup) {
   2170          kidFrame = mColGroups.FirstChild();
   2171          lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this);
   2172        } else {
   2173          kidFrame = mFrames.FirstChild();
   2174        }
   2175        // Important: need to start at a value smaller than all valid indices
   2176        Maybe<uint32_t> lastIndex;
   2177        while (kidFrame) {
   2178          if (isColGroup) {
   2179            if (kidFrame == lastColGroup) {
   2180              aPrevFrame =
   2181                  kidFrame;  // there is no real colgroup after this one
   2182              break;
   2183            }
   2184          }
   2185          pseudoFrame = kidFrame;
   2186          while (pseudoFrame &&
   2187                 (parentContent == (content = pseudoFrame->GetContent()))) {
   2188            pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
   2189          }
   2190          const Maybe<uint32_t> index = container->ComputeIndexOf(content);
   2191          // XXX Keep the odd traditional behavior in some indices are nothing
   2192          //     cases for now.
   2193          if ((index.isSome() &&
   2194               (lastIndex.isNothing() || *index > *lastIndex)) &&
   2195              (newIndex.isSome() &&
   2196               (index.isNothing() || *index < *newIndex))) {
   2197            lastIndex = index;
   2198            aPrevFrame = kidFrame;
   2199          }
   2200          kidFrame = kidFrame->GetNextSibling();
   2201        }
   2202      }
   2203    }
   2204  }
   2205  if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
   2206    NS_ASSERTION(aListID == FrameChildListID::ColGroup,
   2207                 "unexpected child list");
   2208    // Insert the column group frames
   2209    const nsFrameList::Slice& newColgroups =
   2210        mColGroups.InsertFrames(this, aPrevFrame, std::move(aFrameList));
   2211    // find the starting col index for the first new col group
   2212    int32_t startColIndex = 0;
   2213    if (aPrevFrame) {
   2214      nsTableColGroupFrame* prevColGroup =
   2215          (nsTableColGroupFrame*)GetFrameAtOrBefore(
   2216              this, aPrevFrame, LayoutFrameType::TableColGroup);
   2217      if (prevColGroup) {
   2218        startColIndex =
   2219            prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount();
   2220      }
   2221    }
   2222    InsertColGroups(startColIndex, newColgroups);
   2223  } else if (IsRowGroup(display->mDisplay)) {
   2224    NS_ASSERTION(aListID == FrameChildListID::Principal,
   2225                 "unexpected child list");
   2226    DrainSelfOverflowList();  // ensure aPrevFrame is in mFrames
   2227    // Insert the frames in the sibling chain
   2228    const nsFrameList::Slice& newRowGroups =
   2229        mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
   2230 
   2231    InsertRowGroups(newRowGroups);
   2232  } else {
   2233    NS_ASSERTION(aListID == FrameChildListID::Principal,
   2234                 "unexpected child list");
   2235    MOZ_ASSERT_UNREACHABLE("How did we even get here?");
   2236    // Just insert the frame and don't worry about reflowing it
   2237    mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
   2238    return;
   2239  }
   2240 
   2241  PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
   2242                                NS_FRAME_HAS_DIRTY_CHILDREN);
   2243  SetGeometryDirty();
   2244 #ifdef DEBUG_TABLE_CELLMAP
   2245  printf("=== TableFrame::InsertFrames\n");
   2246  Dump(true, true, true);
   2247 #endif
   2248 }
   2249 
   2250 void nsTableFrame::DoRemoveFrame(DestroyContext& aContext, ChildListID aListID,
   2251                                 nsIFrame* aOldFrame) {
   2252  if (aListID == FrameChildListID::ColGroup) {
   2253    nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling();
   2254    nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame;
   2255    int32_t firstColIndex = colGroup->GetStartColumnIndex();
   2256    int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1;
   2257    mColGroups.DestroyFrame(aContext, aOldFrame);
   2258    nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex);
   2259    // remove the cols from the table
   2260    int32_t colIdx;
   2261    for (colIdx = lastColIndex; colIdx >= firstColIndex; colIdx--) {
   2262      nsTableColFrame* colFrame = mColFrames.SafeElementAt(colIdx);
   2263      if (colFrame) {
   2264        RemoveCol(colGroup, colIdx, true, false);
   2265      }
   2266    }
   2267 
   2268    // If we have some anonymous cols at the end already, we just
   2269    // add more of them.
   2270    if (!mColFrames.IsEmpty() &&
   2271        mColFrames.LastElement() &&  // XXXbz is this ever null?
   2272        mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
   2273      int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length();
   2274      if (numAnonymousColsToAdd > 0) {
   2275        // this sets the child list, updates the col cache and cell map
   2276        AppendAnonymousColFrames(numAnonymousColsToAdd);
   2277      }
   2278    } else {
   2279      // All of our colframes correspond to actual <col> tags.  It's possible
   2280      // that we still have at least as many <col> tags as we have logical
   2281      // columns from cells, but we might have one less.  Handle the latter case
   2282      // as follows: First ask the cellmap to drop its last col if it doesn't
   2283      // have any actual cells in it.  Then call MatchCellMapToColCache to
   2284      // append an anonymous column if it's needed; this needs to be after
   2285      // RemoveColsAtEnd, since it will determine the need for a new column
   2286      // frame based on the width of the cell map.
   2287      nsTableCellMap* cellMap = GetCellMap();
   2288      if (cellMap) {  // XXXbz is this ever null?
   2289        cellMap->RemoveColsAtEnd();
   2290        MatchCellMapToColCache(cellMap);
   2291      }
   2292    }
   2293 
   2294  } else {
   2295    NS_ASSERTION(aListID == FrameChildListID::Principal,
   2296                 "unexpected child list");
   2297    nsTableRowGroupFrame* rgFrame =
   2298        static_cast<nsTableRowGroupFrame*>(aOldFrame);
   2299    // remove the row group from the cell map
   2300    nsTableCellMap* cellMap = GetCellMap();
   2301    if (cellMap) {
   2302      cellMap->RemoveGroupCellMap(rgFrame);
   2303    }
   2304 
   2305    // remove the row group frame from the sibling chain
   2306    mFrames.DestroyFrame(aContext, aOldFrame);
   2307 
   2308    // the removal of a row group changes the cellmap, the columns might change
   2309    if (cellMap) {
   2310      cellMap->Synchronize(this);
   2311      // Create an empty slice
   2312      ResetRowIndices(nsFrameList::Slice(nullptr, nullptr));
   2313      TableArea damageArea;
   2314      cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false,
   2315                                       damageArea);
   2316 
   2317      static_cast<nsTableFrame*>(FirstInFlow())
   2318          ->MatchCellMapToColCache(cellMap);
   2319    }
   2320  }
   2321 }
   2322 
   2323 void nsTableFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
   2324                               nsIFrame* aOldFrame) {
   2325  NS_ASSERTION(aListID == FrameChildListID::ColGroup ||
   2326                   mozilla::StyleDisplay::TableColumnGroup !=
   2327                       aOldFrame->StyleDisplay()->mDisplay,
   2328               "Wrong list name; use FrameChildListID::ColGroup iff colgroup");
   2329  mozilla::PresShell* presShell = PresShell();
   2330  nsTableFrame* lastParent = nullptr;
   2331  while (aOldFrame) {
   2332    nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation();
   2333    nsTableFrame* parent = static_cast<nsTableFrame*>(aOldFrame->GetParent());
   2334    if (parent != lastParent) {
   2335      parent->DrainSelfOverflowList();
   2336    }
   2337    parent->DoRemoveFrame(aContext, aListID, aOldFrame);
   2338    aOldFrame = oldFrameNextContinuation;
   2339    if (parent != lastParent) {
   2340      // for now, just bail and recalc all of the collapsing borders
   2341      // as the cellmap changes we need to recalc
   2342      if (parent->IsBorderCollapse()) {
   2343        parent->SetFullBCDamageArea();
   2344      }
   2345      parent->SetGeometryDirty();
   2346      presShell->FrameNeedsReflow(parent, IntrinsicDirty::FrameAndAncestors,
   2347                                  NS_FRAME_HAS_DIRTY_CHILDREN);
   2348      lastParent = parent;
   2349    }
   2350  }
   2351 #ifdef DEBUG_TABLE_CELLMAP
   2352  printf("=== TableFrame::RemoveFrame\n");
   2353  Dump(true, true, true);
   2354 #endif
   2355 }
   2356 
   2357 /* virtual */
   2358 nsMargin nsTableFrame::GetUsedBorder() const {
   2359  if (!IsBorderCollapse()) {
   2360    return nsContainerFrame::GetUsedBorder();
   2361  }
   2362 
   2363  WritingMode wm = GetWritingMode();
   2364  return GetOuterBCBorder(wm).GetPhysicalMargin(wm);
   2365 }
   2366 
   2367 /* virtual */
   2368 nsMargin nsTableFrame::GetUsedPadding() const {
   2369  if (!IsBorderCollapse()) {
   2370    return nsContainerFrame::GetUsedPadding();
   2371  }
   2372 
   2373  return nsMargin(0, 0, 0, 0);
   2374 }
   2375 
   2376 /* virtual */
   2377 nsMargin nsTableFrame::GetUsedMargin() const {
   2378  // The margin is inherited to the table wrapper frame via
   2379  // the ::-moz-table-wrapper rule in ua.css.
   2380  return nsMargin(0, 0, 0, 0);
   2381 }
   2382 
   2383 // TODO(TYLin, dshin): This ideally should be set only in first-in-flow.
   2384 // However, the current implementation of border-collapsed table does not
   2385 // handle continuation gracefully. One concrete issue is shown in bug 1881157
   2386 // comment 3. It is also unclear if the damage area, current included in this
   2387 // property, should be stored separately per-continuation.
   2388 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCDataProperty, TableBCData)
   2389 
   2390 TableBCData* nsTableFrame::GetTableBCData() const {
   2391  return GetProperty(TableBCDataProperty());
   2392 }
   2393 
   2394 TableBCData* nsTableFrame::GetOrCreateTableBCData() {
   2395  TableBCData* value = GetProperty(TableBCDataProperty());
   2396  if (!value) {
   2397    value = new TableBCData();
   2398    SetProperty(TableBCDataProperty(), value);
   2399  }
   2400 
   2401  MOZ_ASSERT(value, "TableBCData must exist!");
   2402  return value;
   2403 }
   2404 
   2405 static void DivideBCBorderSize(nscoord aPixelSize, nscoord& aSmallHalf,
   2406                               nscoord& aLargeHalf) {
   2407  aSmallHalf = aPixelSize / 2;
   2408  aLargeHalf = aPixelSize - aSmallHalf;
   2409 }
   2410 
   2411 LogicalMargin nsTableFrame::GetOuterBCBorder(const WritingMode aWM) const {
   2412  if (NeedToCalcBCBorders()) {
   2413    const_cast<nsTableFrame*>(this)->CalcBCBorders();
   2414  }
   2415  TableBCData* propData = GetTableBCData();
   2416  if (propData) {
   2417    return LogicalMargin(aWM,
   2418                         BC_BORDER_START_HALF(propData->mBStartBorderWidth),
   2419                         BC_BORDER_END_HALF(propData->mIEndBorderWidth),
   2420                         BC_BORDER_END_HALF(propData->mBEndBorderWidth),
   2421                         BC_BORDER_START_HALF(propData->mIStartBorderWidth));
   2422  }
   2423  return LogicalMargin(aWM);
   2424 }
   2425 
   2426 void nsTableFrame::GetCollapsedBorderPadding(
   2427    Maybe<LogicalMargin>& aBorder, Maybe<LogicalMargin>& aPadding) const {
   2428  if (IsBorderCollapse()) {
   2429    // Border-collapsed tables don't use any of their padding, and only part of
   2430    // their border.
   2431    const auto wm = GetWritingMode();
   2432    aBorder.emplace(GetOuterBCBorder(wm));
   2433    aPadding.emplace(wm);
   2434  }
   2435 }
   2436 
   2437 void nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput) {
   2438  const auto childWM = aReflowInput.GetWritingMode();
   2439  LogicalMargin border(childWM);
   2440  if (IsBorderCollapse()) {
   2441    nsTableRowGroupFrame* rgFrame =
   2442        static_cast<nsTableRowGroupFrame*>(aReflowInput.mFrame);
   2443    border = rgFrame->GetBCBorderWidth(childWM);
   2444  }
   2445  const LogicalMargin zeroPadding(childWM);
   2446  aReflowInput.Init(PresContext(), Nothing(), Some(border), Some(zeroPadding));
   2447 
   2448  NS_ASSERTION(!mBits.mResizedColumns ||
   2449                   !aReflowInput.mParentReflowInput->mFlags.mSpecialBSizeReflow,
   2450               "should not resize columns on special bsize reflow");
   2451  if (mBits.mResizedColumns) {
   2452    aReflowInput.SetIResize(true);
   2453  }
   2454 }
   2455 
   2456 // Position and size aKidFrame and update our reflow input. The origin of
   2457 // aKidRect is relative to the upper-left origin of our frame
   2458 void nsTableFrame::PlaceChild(TableReflowInput& aReflowInput,
   2459                              nsIFrame* aKidFrame,
   2460                              const ReflowInput& aKidReflowInput,
   2461                              const mozilla::LogicalPoint& aKidPosition,
   2462                              const nsSize& aContainerSize,
   2463                              ReflowOutput& aKidDesiredSize,
   2464                              const nsRect& aOriginalKidRect,
   2465                              const nsRect& aOriginalKidInkOverflow) {
   2466  WritingMode wm = aReflowInput.mReflowInput.GetWritingMode();
   2467  bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
   2468 
   2469  // Place and size the child
   2470  FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, &aKidReflowInput,
   2471                    wm, aKidPosition, aContainerSize,
   2472                    ReflowChildFlags::ApplyRelativePositioning);
   2473 
   2474  InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidInkOverflow,
   2475                       isFirstReflow);
   2476 
   2477  aReflowInput.AdvanceBCoord(aKidDesiredSize.BSize(wm));
   2478 }
   2479 
   2480 nsTableFrame::RowGroupArray nsTableFrame::OrderedRowGroups(
   2481    nsTableRowGroupFrame** aHead, nsTableRowGroupFrame** aFoot) const {
   2482  RowGroupArray children;
   2483  nsTableRowGroupFrame* head = nullptr;
   2484  nsTableRowGroupFrame* foot = nullptr;
   2485 
   2486  nsIFrame* kidFrame = mFrames.FirstChild();
   2487  while (kidFrame) {
   2488    const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay();
   2489    auto* rowGroup = static_cast<nsTableRowGroupFrame*>(kidFrame);
   2490 
   2491    switch (kidDisplay->DisplayInside()) {
   2492      case StyleDisplayInside::TableHeaderGroup:
   2493        if (head) {  // treat additional thead like tbody
   2494          children.AppendElement(rowGroup);
   2495        } else {
   2496          head = rowGroup;
   2497        }
   2498        break;
   2499      case StyleDisplayInside::TableFooterGroup:
   2500        if (foot) {  // treat additional tfoot like tbody
   2501          children.AppendElement(rowGroup);
   2502        } else {
   2503          foot = rowGroup;
   2504        }
   2505        break;
   2506      case StyleDisplayInside::TableRowGroup:
   2507        children.AppendElement(rowGroup);
   2508        break;
   2509      default:
   2510        MOZ_ASSERT_UNREACHABLE("How did this produce an nsTableRowGroupFrame?");
   2511        // Just ignore it
   2512        break;
   2513    }
   2514    // Get the next sibling but skip it if it's also the next-in-flow, since
   2515    // a next-in-flow will not be part of the current table.
   2516    while (kidFrame) {
   2517      nsIFrame* nif = kidFrame->GetNextInFlow();
   2518      kidFrame = kidFrame->GetNextSibling();
   2519      if (kidFrame != nif) {
   2520        break;
   2521      }
   2522    }
   2523  }
   2524 
   2525  // put the thead first
   2526  if (head) {
   2527    children.InsertElementAt(0, head);
   2528  }
   2529  if (aHead) {
   2530    *aHead = head;
   2531  }
   2532  // put the tfoot after the last tbody
   2533  if (foot) {
   2534    children.AppendElement(foot);
   2535  }
   2536  if (aFoot) {
   2537    *aFoot = foot;
   2538  }
   2539 
   2540  return children;
   2541 }
   2542 
   2543 static bool IsRepeatable(nscoord aFrameBSize, nscoord aPageBSize) {
   2544  return aFrameBSize < (aPageBSize / 4);
   2545 }
   2546 
   2547 nscoord nsTableFrame::SetupHeaderFooterChild(
   2548    const TableReflowInput& aReflowInput, nsTableRowGroupFrame* aFrame) {
   2549  nsPresContext* presContext = PresContext();
   2550  const WritingMode wm = GetWritingMode();
   2551  const nscoord pageBSize =
   2552      LogicalSize(wm, presContext->GetPageSize()).BSize(wm);
   2553 
   2554  // Reflow the child with unconstrained block-size.
   2555  LogicalSize availSize = aReflowInput.AvailableSize();
   2556  availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
   2557 
   2558  const nsSize containerSize =
   2559      aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained();
   2560  ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput, aFrame,
   2561                             availSize, Nothing(),
   2562                             ReflowInput::InitFlag::CallerWillInit);
   2563  InitChildReflowInput(kidReflowInput);
   2564  kidReflowInput.mFlags.mIsTopOfPage = true;
   2565  ReflowOutput desiredSize(aReflowInput.mReflowInput);
   2566  nsReflowStatus status;
   2567  ReflowChild(aFrame, presContext, desiredSize, kidReflowInput, wm,
   2568              LogicalPoint(wm, aReflowInput.mICoord, aReflowInput.mBCoord),
   2569              containerSize, ReflowChildFlags::Default, status);
   2570  // The child will be reflowed again "for real" so no need to place it now
   2571 
   2572  aFrame->SetRepeatable(IsRepeatable(desiredSize.BSize(wm), pageBSize));
   2573  return desiredSize.BSize(wm);
   2574 }
   2575 
   2576 void nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput,
   2577                                       nsTableRowGroupFrame* aTfoot,
   2578                                       nscoord aFooterBSize) {
   2579  nsPresContext* presContext = PresContext();
   2580  const WritingMode wm = GetWritingMode();
   2581  LogicalSize kidAvailSize = aReflowInput.AvailableSize();
   2582  kidAvailSize.BSize(wm) = aFooterBSize;
   2583 
   2584  const nsSize containerSize =
   2585      aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained();
   2586  ReflowInput footerReflowInput(presContext, aReflowInput.mReflowInput, aTfoot,
   2587                                kidAvailSize, Nothing(),
   2588                                ReflowInput::InitFlag::CallerWillInit);
   2589  InitChildReflowInput(footerReflowInput);
   2590 
   2591  nsRect origTfootRect = aTfoot->GetRect();
   2592  nsRect origTfootInkOverflow = aTfoot->InkOverflowRect();
   2593 
   2594  nsReflowStatus footerStatus;
   2595  ReflowOutput desiredSize(aReflowInput.mReflowInput);
   2596  LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord);
   2597  ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput, wm,
   2598              kidPosition, containerSize, ReflowChildFlags::Default,
   2599              footerStatus);
   2600 
   2601  PlaceChild(aReflowInput, aTfoot, footerReflowInput, kidPosition,
   2602             containerSize, desiredSize, origTfootRect, origTfootInkOverflow);
   2603 }
   2604 
   2605 // Reflow the children based on the avail size and reason in aReflowInput
   2606 void nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput,
   2607                                  nsReflowStatus& aStatus,
   2608                                  nsIFrame*& aLastChildReflowed,
   2609                                  OverflowAreas& aOverflowAreas) {
   2610  aStatus.Reset();
   2611  aLastChildReflowed = nullptr;
   2612 
   2613  nsIFrame* prevKidFrame = nullptr;
   2614  WritingMode wm = aReflowInput.mReflowInput.GetWritingMode();
   2615  NS_WARNING_ASSERTION(
   2616      wm.IsVertical() ||
   2617          NS_UNCONSTRAINEDSIZE != aReflowInput.mReflowInput.ComputedWidth(),
   2618      "shouldn't have unconstrained width in horizontal mode");
   2619  nsSize containerSize =
   2620      aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained();
   2621 
   2622  nsPresContext* presContext = PresContext();
   2623  // nsTableFrame is not able to pull back children from its next-in-flow, per
   2624  // bug 1772383.  So even under paginated contexts, tables should not fragment
   2625  // if they are inside of (i.e. potentially being fragmented by) a column-set
   2626  // frame.  (This is indicated by the "mTableIsSplittable" flag.)
   2627  bool isPaginated =
   2628      presContext->IsPaginated() &&
   2629      aReflowInput.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
   2630      aReflowInput.mReflowInput.mFlags.mTableIsSplittable;
   2631 
   2632  // Tables currently (though we ought to fix this) only fragment in
   2633  // paginated contexts, not in multicolumn contexts.  (See bug 888257.)
   2634  // This is partly because they don't correctly handle incremental
   2635  // layout when paginated.
   2636  //
   2637  // Since we propagate NS_FRAME_IS_DIRTY from parent to child at the
   2638  // start of the parent's reflow (behavior that's new as of bug
   2639  // 1308876), we can do things that are effectively incremental reflow
   2640  // during paginated layout.  Since the table code doesn't handle this
   2641  // correctly, we need to set the flag that says to reflow everything
   2642  // within the table structure.
   2643  if (presContext->IsPaginated()) {
   2644    SetGeometryDirty();
   2645  }
   2646 
   2647  aOverflowAreas.Clear();
   2648 
   2649  bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() ||
   2650                       mBits.mResizedColumns || IsGeometryDirty() ||
   2651                       NeedToCollapse();
   2652 
   2653  nsTableRowGroupFrame* thead = nullptr;
   2654  nsTableRowGroupFrame* tfoot = nullptr;
   2655  RowGroupArray rowGroups = OrderedRowGroups(&thead, &tfoot);
   2656  bool pageBreak = false;
   2657  nscoord footerBSize = 0;
   2658 
   2659  // Determine the repeatablility of headers and footers, and also the desired
   2660  // height of any repeatable footer.
   2661  // The repeatability of headers on continued tables is handled
   2662  // when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame.
   2663  // We handle the repeatability of footers again here because we need to
   2664  // determine the footer's height anyway. We could perhaps optimize by
   2665  // using the footer's prev-in-flow's height instead of reflowing it again,
   2666  // but there's no real need.
   2667  if (isPaginated) {
   2668    bool reorder = false;
   2669    if (thead && !GetPrevInFlow()) {
   2670      reorder = thead->GetNextInFlow();
   2671      SetupHeaderFooterChild(aReflowInput, thead);
   2672    }
   2673    if (tfoot) {
   2674      reorder = reorder || tfoot->GetNextInFlow();
   2675      footerBSize = SetupHeaderFooterChild(aReflowInput, tfoot);
   2676    }
   2677    if (reorder) {
   2678      // Reorder row groups - the reflow may have changed the nextinflows.
   2679      rowGroups = OrderedRowGroups(&thead, &tfoot);
   2680    }
   2681  }
   2682  bool allowRepeatedFooter = false;
   2683  for (size_t childX = 0; childX < rowGroups.Length(); childX++) {
   2684    nsTableRowGroupFrame* kidFrame = rowGroups[childX];
   2685    const nscoord rowSpacing =
   2686        GetRowSpacing(kidFrame->GetStartRowIndex() + kidFrame->GetRowCount());
   2687    // See if we should only reflow the dirty child frames
   2688    if (reflowAllKids || kidFrame->IsSubtreeDirty() ||
   2689        (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow &&
   2690         (isPaginated ||
   2691          kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
   2692      // A helper to place a repeated footer if allowed, or set it as
   2693      // non-repeatable.
   2694      auto MaybePlaceRepeatedFooter = [&]() {
   2695        if (allowRepeatedFooter) {
   2696          PlaceRepeatedFooter(aReflowInput, tfoot, footerBSize);
   2697        } else if (tfoot && tfoot->IsRepeatable()) {
   2698          tfoot->SetRepeatable(false);
   2699        }
   2700      };
   2701 
   2702      if (pageBreak) {
   2703        MaybePlaceRepeatedFooter();
   2704        PushChildrenToOverflow(rowGroups, childX);
   2705        aStatus.Reset();
   2706        aStatus.SetIncomplete();
   2707        aLastChildReflowed = allowRepeatedFooter ? tfoot : prevKidFrame;
   2708        break;
   2709      }
   2710 
   2711      LogicalSize kidAvailSize = aReflowInput.AvailableSize();
   2712      allowRepeatedFooter = false;
   2713 
   2714      // If the child is a tbody in paginated mode, reduce the available
   2715      // block-size by a repeated footer.
   2716      if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) {
   2717        if (kidFrame != thead && kidFrame != tfoot && tfoot &&
   2718            tfoot->IsRepeatable()) {
   2719          // the child is a tbody and there is a repeatable footer
   2720          NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1],
   2721                       "Missing footer!");
   2722          if (footerBSize + rowSpacing < kidAvailSize.BSize(wm)) {
   2723            allowRepeatedFooter = true;
   2724            kidAvailSize.BSize(wm) -= footerBSize + rowSpacing;
   2725          }
   2726        }
   2727      }
   2728 
   2729      nsRect oldKidRect = kidFrame->GetRect();
   2730      nsRect oldKidInkOverflow = kidFrame->InkOverflowRect();
   2731 
   2732      ReflowOutput desiredSize(aReflowInput.mReflowInput);
   2733 
   2734      // Reflow the child into the available space
   2735      ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput,
   2736                                 kidFrame, kidAvailSize, Nothing(),
   2737                                 ReflowInput::InitFlag::CallerWillInit);
   2738      InitChildReflowInput(kidReflowInput);
   2739 
   2740      // If this isn't the first row group, and the previous row group has a
   2741      // nonzero BEnd, then we can't be at the top of the page.
   2742      // We ignore a repeated head row group in this check to avoid causing
   2743      // infinite loops in some circumstances - see bug 344883.
   2744      if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) &&
   2745          (rowGroups[childX - 1]
   2746               ->GetLogicalNormalRect(wm, containerSize)
   2747               .BEnd(wm) > 0)) {
   2748        kidReflowInput.mFlags.mIsTopOfPage = false;
   2749      }
   2750 
   2751      // record the presence of a next in flow, it might get destroyed so we
   2752      // need to reorder the row group array
   2753      const bool reorder = kidFrame->GetNextInFlow();
   2754 
   2755      LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord);
   2756      aStatus.Reset();
   2757      ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput, wm,
   2758                  kidPosition, containerSize, ReflowChildFlags::Default,
   2759                  aStatus);
   2760 
   2761      if (reorder) {
   2762        // Reorder row groups - the reflow may have changed the nextinflows.
   2763        rowGroups = OrderedRowGroups(&thead, &tfoot);
   2764        childX = rowGroups.IndexOf(kidFrame);
   2765        MOZ_ASSERT(childX != RowGroupArray::NoIndex,
   2766                   "kidFrame should still be in rowGroups!");
   2767      }
   2768      if (isPaginated && !aStatus.IsFullyComplete() &&
   2769          ShouldAvoidBreakInside(aReflowInput.mReflowInput)) {
   2770        aStatus.SetInlineLineBreakBeforeAndReset();
   2771        break;
   2772      }
   2773      // see if the rowgroup did not fit on this page might be pushed on
   2774      // the next page
   2775      if (isPaginated &&
   2776          (aStatus.IsInlineBreakBefore() ||
   2777           (aStatus.IsComplete() &&
   2778            (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
   2779            kidReflowInput.AvailableBSize() < desiredSize.BSize(wm)))) {
   2780        if (ShouldAvoidBreakInside(aReflowInput.mReflowInput)) {
   2781          aStatus.SetInlineLineBreakBeforeAndReset();
   2782          break;
   2783        }
   2784        // if we are on top of the page place with dataloss
   2785        if (kidReflowInput.mFlags.mIsTopOfPage) {
   2786          if (childX + 1 < rowGroups.Length()) {
   2787            PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition,
   2788                       containerSize, desiredSize, oldKidRect,
   2789                       oldKidInkOverflow);
   2790            MaybePlaceRepeatedFooter();
   2791            aStatus.Reset();
   2792            aStatus.SetIncomplete();
   2793            PushChildrenToOverflow(rowGroups, childX + 1);
   2794            aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame;
   2795            break;
   2796          }
   2797        } else {  // we are not on top, push this rowgroup onto the next page
   2798          if (prevKidFrame) {  // we had a rowgroup before so push this
   2799            MaybePlaceRepeatedFooter();
   2800            aStatus.Reset();
   2801            aStatus.SetIncomplete();
   2802            PushChildrenToOverflow(rowGroups, childX);
   2803            aLastChildReflowed = allowRepeatedFooter ? tfoot : prevKidFrame;
   2804            break;
   2805          } else {  // we can't push so lets make clear how much space we need
   2806            PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition,
   2807                       containerSize, desiredSize, oldKidRect,
   2808                       oldKidInkOverflow);
   2809            MaybePlaceRepeatedFooter();
   2810            aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame;
   2811            break;
   2812          }
   2813        }
   2814      }
   2815 
   2816      aLastChildReflowed = kidFrame;
   2817 
   2818      pageBreak = false;
   2819      // see if there is a page break after this row group or before the next
   2820      // one
   2821      if (aStatus.IsComplete() && isPaginated &&
   2822          (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) {
   2823        nsIFrame* nextKid =
   2824            (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr;
   2825        pageBreak = PageBreakAfter(kidFrame, nextKid);
   2826      }
   2827 
   2828      // Place the child
   2829      PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition,
   2830                 containerSize, desiredSize, oldKidRect, oldKidInkOverflow);
   2831      aReflowInput.AdvanceBCoord(rowSpacing);
   2832 
   2833      // Remember where we just were in case we end up pushing children
   2834      prevKidFrame = kidFrame;
   2835 
   2836      MOZ_ASSERT(!aStatus.IsIncomplete() || isPaginated,
   2837                 "Table contents should only fragment in paginated contexts");
   2838 
   2839      // Special handling for incomplete children
   2840      if (isPaginated && aStatus.IsIncomplete()) {
   2841        nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();
   2842        if (!kidNextInFlow) {
   2843          // The child doesn't have a next-in-flow so create a continuing
   2844          // frame. This hooks the child into the flow
   2845          kidNextInFlow =
   2846              PresShell()->FrameConstructor()->CreateContinuingFrame(kidFrame,
   2847                                                                     this);
   2848 
   2849          // Insert the kid's new next-in-flow into our sibling list...
   2850          mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow);
   2851          // and in rowGroups after childX so that it will get pushed below.
   2852          rowGroups.InsertElementAt(
   2853              childX + 1, static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
   2854        } else if (kidNextInFlow == kidFrame->GetNextSibling()) {
   2855          // OrderedRowGroups excludes NIFs in the child list from 'rowGroups'
   2856          // so we deal with that here to make sure they get pushed.
   2857          MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow),
   2858                     "OrderedRowGroups must not put our NIF in 'rowGroups'");
   2859          rowGroups.InsertElementAt(
   2860              childX + 1, static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
   2861        }
   2862 
   2863        // We've used up all of our available space so push the remaining
   2864        // children.
   2865        MaybePlaceRepeatedFooter();
   2866        if (kidFrame->GetNextSibling()) {
   2867          PushChildrenToOverflow(rowGroups, childX + 1);
   2868        }
   2869        aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame;
   2870        break;
   2871      }
   2872    } else {  // it isn't being reflowed
   2873      aReflowInput.AdvanceBCoord(rowSpacing);
   2874      const LogicalRect kidRect =
   2875          kidFrame->GetLogicalNormalRect(wm, containerSize);
   2876      if (kidRect.BStart(wm) != aReflowInput.mBCoord) {
   2877        // invalidate the old position
   2878        kidFrame->InvalidateFrameSubtree();
   2879        // move to the new position
   2880        kidFrame->MovePositionBy(
   2881            wm, LogicalPoint(wm, 0, aReflowInput.mBCoord - kidRect.BStart(wm)));
   2882        // invalidate the new position
   2883        kidFrame->InvalidateFrameSubtree();
   2884      }
   2885 
   2886      aReflowInput.AdvanceBCoord(kidRect.BSize(wm));
   2887    }
   2888  }
   2889 
   2890  // We've now propagated the column resizes and geometry changes to all
   2891  // the children.
   2892  mBits.mResizedColumns = false;
   2893  ClearGeometryDirty();
   2894 
   2895  // nsTableFrame does not pull children from its next-in-flow (bug 1772383).
   2896  // This is generally fine, since tables only fragment for printing
   2897  // (bug 888257) where incremental-reflow is impossible, and so children don't
   2898  // usually dynamically move back and forth between continuations. However,
   2899  // there are edge cases even with printing where nsTableFrame:
   2900  // (1) Generates a continuation and passes children to it,
   2901  // (2) Receives another call to Reflow, during which it
   2902  // (3) Successfully lays out its remaining children.
   2903  // If the completed status flows up as-is, the continuation will be destroyed.
   2904  // To avoid that, we return an incomplete status if the continuation contains
   2905  // any child that is not a repeated frame.
   2906  auto hasNextInFlowThatMustBePreserved = [this, isPaginated]() -> bool {
   2907    if (!isPaginated) {
   2908      return false;
   2909    }
   2910    auto* nextInFlow = static_cast<nsTableFrame*>(GetNextInFlow());
   2911    if (!nextInFlow) {
   2912      return false;
   2913    }
   2914    for (nsIFrame* kidFrame : nextInFlow->mFrames) {
   2915      if (!IsRepeatedFrame(kidFrame)) {
   2916        return true;
   2917      }
   2918    }
   2919    return false;
   2920  };
   2921  if (aStatus.IsComplete() && hasNextInFlowThatMustBePreserved()) {
   2922    aStatus.SetIncomplete();
   2923  }
   2924 }
   2925 
   2926 void nsTableFrame::ReflowColGroups(gfxContext* aRenderingContext) {
   2927  if (!GetPrevInFlow() && !HaveReflowedColGroups()) {
   2928    const WritingMode wm = GetWritingMode();
   2929    nsPresContext* presContext = PresContext();
   2930    for (nsIFrame* kidFrame : mColGroups) {
   2931      if (kidFrame->IsSubtreeDirty()) {
   2932        // The column groups don't care about dimensions or reflow inputs.
   2933        ReflowOutput kidSize(wm);
   2934        ReflowInput kidReflowInput(presContext, kidFrame, aRenderingContext,
   2935                                   LogicalSize(kidFrame->GetWritingMode()));
   2936        nsReflowStatus cgStatus;
   2937        const LogicalPoint dummyPos(wm);
   2938        const nsSize dummyContainerSize;
   2939        ReflowChild(kidFrame, presContext, kidSize, kidReflowInput, wm,
   2940                    dummyPos, dummyContainerSize, ReflowChildFlags::Default,
   2941                    cgStatus);
   2942        FinishReflowChild(kidFrame, presContext, kidSize, &kidReflowInput, wm,
   2943                          dummyPos, dummyContainerSize,
   2944                          ReflowChildFlags::Default);
   2945      }
   2946    }
   2947    SetHaveReflowedColGroups(true);
   2948  }
   2949 }
   2950 
   2951 nscoord nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput,
   2952                                       const LogicalMargin& aBorderPadding,
   2953                                       const nsReflowStatus& aStatus) {
   2954  WritingMode wm = aReflowInput.GetWritingMode();
   2955 
   2956  RowGroupArray rowGroups = OrderedRowGroups();
   2957  if (rowGroups.IsEmpty()) {
   2958    if (eCompatibility_NavQuirks == PresContext()->CompatibilityMode()) {
   2959      // empty tables should not have a size in quirks mode
   2960      return 0;
   2961    }
   2962    return CalcBorderBoxBSize(aReflowInput, aBorderPadding,
   2963                              aBorderPadding.BStartEnd(wm));
   2964  }
   2965 
   2966  nsTableCellMap* cellMap = GetCellMap();
   2967  MOZ_ASSERT(cellMap);
   2968  int32_t rowCount = cellMap->GetRowCount();
   2969  int32_t colCount = cellMap->GetColCount();
   2970  nscoord desiredBSize = aBorderPadding.BStartEnd(wm);
   2971  if (rowCount > 0 && colCount > 0) {
   2972    if (!GetPrevInFlow()) {
   2973      desiredBSize += GetRowSpacing(-1);
   2974    }
   2975    const nsTableRowGroupFrame* lastRG = rowGroups.LastElement();
   2976    for (nsTableRowGroupFrame* rg : rowGroups) {
   2977      desiredBSize += rg->BSize(wm);
   2978      if (rg != lastRG || aStatus.IsFullyComplete()) {
   2979        desiredBSize +=
   2980            GetRowSpacing(rg->GetStartRowIndex() + rg->GetRowCount());
   2981      }
   2982    }
   2983    if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE &&
   2984        aStatus.IsIncomplete()) {
   2985      desiredBSize = std::max(desiredBSize, aReflowInput.AvailableBSize());
   2986    }
   2987  }
   2988 
   2989  // see if a specified table bsize requires dividing additional space to rows
   2990  if (!GetPrevInFlow()) {
   2991    nscoord bSize =
   2992        CalcBorderBoxBSize(aReflowInput, aBorderPadding, desiredBSize);
   2993    if (bSize > desiredBSize) {
   2994      // proportionately distribute the excess bsize to unconstrained rows in
   2995      // each unconstrained row group.
   2996      DistributeBSizeToRows(aReflowInput, bSize - desiredBSize);
   2997      return bSize;
   2998    }
   2999    // Tables don't shrink below their intrinsic size, apparently, even when
   3000    // constrained by stuff like flex / grid or what not.
   3001    return desiredBSize;
   3002  }
   3003 
   3004  // FIXME(emilio): Is this right? This only affects fragmented tables...
   3005  return desiredBSize;
   3006 }
   3007 
   3008 static void ResizeCells(nsTableFrame& aTableFrame) {
   3009  nsTableFrame::RowGroupArray rowGroups = aTableFrame.OrderedRowGroups();
   3010  WritingMode wm = aTableFrame.GetWritingMode();
   3011  ReflowOutput tableDesiredSize(wm);
   3012  tableDesiredSize.SetSize(wm, aTableFrame.GetLogicalSize(wm));
   3013  tableDesiredSize.SetOverflowAreasToDesiredBounds();
   3014 
   3015  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
   3016    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   3017 
   3018    ReflowOutput groupDesiredSize(wm);
   3019    groupDesiredSize.SetSize(wm, rgFrame->GetLogicalSize(wm));
   3020    groupDesiredSize.SetOverflowAreasToDesiredBounds();
   3021 
   3022    nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
   3023    while (rowFrame) {
   3024      rowFrame->DidResize();
   3025      rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame);
   3026      rowFrame = rowFrame->GetNextRow();
   3027    }
   3028    rgFrame->FinishAndStoreOverflow(&groupDesiredSize);
   3029    tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas +
   3030                                              rgFrame->GetPosition());
   3031  }
   3032  aTableFrame.FinishAndStoreOverflow(&tableDesiredSize);
   3033 }
   3034 
   3035 void nsTableFrame::DistributeBSizeToRows(const ReflowInput& aReflowInput,
   3036                                         nscoord aAmount) {
   3037  WritingMode wm = aReflowInput.GetWritingMode();
   3038  LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
   3039 
   3040  nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
   3041 
   3042  RowGroupArray rowGroups = OrderedRowGroups();
   3043 
   3044  nscoord amountUsed = 0;
   3045  // distribute space to each pct bsize row whose row group doesn't have a
   3046  // computed bsize, and base the pct on the table bsize. If the row group had a
   3047  // computed bsize, then this was already done in
   3048  // nsTableRowGroupFrame::CalculateRowBSizes
   3049  nscoord pctBasis =
   3050      aReflowInput.ComputedBSize() - GetRowSpacing(-1, GetRowCount());
   3051  nscoord bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(0);
   3052  nscoord bEndRG = bOriginRG;
   3053  uint32_t rgIdx;
   3054  for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
   3055    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   3056    nscoord amountUsedByRG = 0;
   3057    nscoord bOriginRow = 0;
   3058    const LogicalRect rgNormalRect =
   3059        rgFrame->GetLogicalNormalRect(wm, containerSize);
   3060    if (!rgFrame->HasStyleBSize()) {
   3061      nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
   3062      while (rowFrame) {
   3063        // We don't know the final width of the rowGroupFrame yet, so use 0,0
   3064        // as a dummy containerSize here; we'll adjust the row positions at
   3065        // the end, after the rowGroup size is finalized.
   3066        const nsSize dummyContainerSize;
   3067        const LogicalRect rowNormalRect =
   3068            rowFrame->GetLogicalNormalRect(wm, dummyContainerSize);
   3069        const nscoord rowSpacing = GetRowSpacing(rowFrame->GetRowIndex());
   3070        if ((amountUsed < aAmount) && rowFrame->HasPctBSize()) {
   3071          nscoord pctBSize = rowFrame->GetInitialBSize(pctBasis);
   3072          nscoord amountForRow = std::min(aAmount - amountUsed,
   3073                                          pctBSize - rowNormalRect.BSize(wm));
   3074          if (amountForRow > 0) {
   3075            // XXXbz we don't need to move the row's b-position to bOriginRow?
   3076            nsRect origRowRect = rowFrame->GetRect();
   3077            nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
   3078            rowFrame->SetSize(
   3079                wm, LogicalSize(wm, rowNormalRect.ISize(wm), newRowBSize));
   3080            bOriginRow += newRowBSize + rowSpacing;
   3081            bEndRG += newRowBSize + rowSpacing;
   3082            amountUsed += amountForRow;
   3083            amountUsedByRG += amountForRow;
   3084            // rowFrame->DidResize();
   3085 
   3086            rgFrame->InvalidateFrameWithRect(origRowRect);
   3087            rgFrame->InvalidateFrame();
   3088          }
   3089        } else {
   3090          if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm) &&
   3091              !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
   3092            rowFrame->InvalidateFrameSubtree();
   3093            rowFrame->MovePositionBy(
   3094                wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm)));
   3095            rowFrame->InvalidateFrameSubtree();
   3096          }
   3097          bOriginRow += rowNormalRect.BSize(wm) + rowSpacing;
   3098          bEndRG += rowNormalRect.BSize(wm) + rowSpacing;
   3099        }
   3100        rowFrame = rowFrame->GetNextRow();
   3101      }
   3102      if (amountUsed > 0) {
   3103        if (rgNormalRect.BStart(wm) != bOriginRG) {
   3104          rgFrame->InvalidateFrameSubtree();
   3105        }
   3106 
   3107        nsRect origRgNormalRect = rgFrame->GetRect();
   3108        nsRect origRgInkOverflow = rgFrame->InkOverflowRect();
   3109 
   3110        rgFrame->MovePositionBy(
   3111            wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
   3112        rgFrame->SetSize(wm,
   3113                         LogicalSize(wm, rgNormalRect.ISize(wm),
   3114                                     rgNormalRect.BSize(wm) + amountUsedByRG));
   3115 
   3116        nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
   3117                                           origRgInkOverflow, false);
   3118      }
   3119    } else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
   3120      rgFrame->InvalidateFrameSubtree();
   3121      rgFrame->MovePositionBy(
   3122          wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
   3123      // Make sure child views are properly positioned
   3124      rgFrame->InvalidateFrameSubtree();
   3125    }
   3126    bOriginRG = bEndRG;
   3127  }
   3128 
   3129  if (amountUsed >= aAmount) {
   3130    ResizeCells(*this);
   3131    return;
   3132  }
   3133 
   3134  // get the first row without a style bsize where its row group has an
   3135  // unconstrained bsize
   3136  nsTableRowGroupFrame* firstUnStyledRG = nullptr;
   3137  nsTableRowFrame* firstUnStyledRow = nullptr;
   3138  for (rgIdx = 0; rgIdx < rowGroups.Length() && !firstUnStyledRG; rgIdx++) {
   3139    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   3140    if (!rgFrame->HasStyleBSize()) {
   3141      nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
   3142      while (rowFrame) {
   3143        if (!rowFrame->HasStyleBSize()) {
   3144          firstUnStyledRG = rgFrame;
   3145          firstUnStyledRow = rowFrame;
   3146          break;
   3147        }
   3148        rowFrame = rowFrame->GetNextRow();
   3149      }
   3150    }
   3151  }
   3152 
   3153  nsTableRowFrame* lastEligibleRow = nullptr;
   3154  // Accumulate the correct divisor. This will be the total bsize of all
   3155  // unstyled rows inside unstyled row groups, unless there are none, in which
   3156  // case, it will be number of all rows. If the unstyled rows don't have a
   3157  // bsize, divide the space equally among them.
   3158  nscoord divisor = 0;
   3159  int32_t eligibleRows = 0;
   3160  bool expandEmptyRows = false;
   3161 
   3162  if (!firstUnStyledRow) {
   3163    // there is no unstyled row
   3164    divisor = GetRowCount();
   3165  } else {
   3166    for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
   3167      nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   3168      if (!firstUnStyledRG || !rgFrame->HasStyleBSize()) {
   3169        nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
   3170        while (rowFrame) {
   3171          if (!firstUnStyledRG || !rowFrame->HasStyleBSize()) {
   3172            NS_ASSERTION(rowFrame->BSize(wm) >= 0,
   3173                         "negative row frame block-size");
   3174            divisor += rowFrame->BSize(wm);
   3175            eligibleRows++;
   3176            lastEligibleRow = rowFrame;
   3177          }
   3178          rowFrame = rowFrame->GetNextRow();
   3179        }
   3180      }
   3181    }
   3182    if (divisor <= 0) {
   3183      if (eligibleRows > 0) {
   3184        expandEmptyRows = true;
   3185      } else {
   3186        NS_ERROR("invalid divisor");
   3187        return;
   3188      }
   3189    }
   3190  }
   3191  // allocate the extra bsize to the unstyled row groups and rows
   3192  nscoord bSizeToDistribute = aAmount - amountUsed;
   3193  bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(-1);
   3194  bEndRG = bOriginRG;
   3195  for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
   3196    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   3197    nscoord amountUsedByRG = 0;
   3198    nscoord bOriginRow = 0;
   3199    const LogicalRect rgNormalRect =
   3200        rgFrame->GetLogicalNormalRect(wm, containerSize);
   3201    nsRect rgInkOverflow = rgFrame->InkOverflowRect();
   3202    // see if there is an eligible row group or we distribute to all rows
   3203    if (!firstUnStyledRG || !rgFrame->HasStyleBSize() || !eligibleRows) {
   3204      for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
   3205           rowFrame = rowFrame->GetNextRow()) {
   3206        const nscoord rowSpacing = GetRowSpacing(rowFrame->GetRowIndex());
   3207        // We don't know the final width of the rowGroupFrame yet, so use 0,0
   3208        // as a dummy containerSize here; we'll adjust the row positions at
   3209        // the end, after the rowGroup size is finalized.
   3210        const nsSize dummyContainerSize;
   3211        const LogicalRect rowNormalRect =
   3212            rowFrame->GetLogicalNormalRect(wm, dummyContainerSize);
   3213        nsRect rowInkOverflow = rowFrame->InkOverflowRect();
   3214        // see if there is an eligible row or we distribute to all rows
   3215        if (!firstUnStyledRow || !rowFrame->HasStyleBSize() || !eligibleRows) {
   3216          float ratio;
   3217          if (eligibleRows) {
   3218            if (!expandEmptyRows) {
   3219              // The amount of additional space each row gets is proportional
   3220              // to its bsize
   3221              ratio = float(rowNormalRect.BSize(wm)) / float(divisor);
   3222            } else {
   3223              // empty rows get all the same additional space
   3224              ratio = 1.0f / float(eligibleRows);
   3225            }
   3226          } else {
   3227            // all rows get the same additional space
   3228            ratio = 1.0f / float(divisor);
   3229          }
   3230          // give rows their additional space, except for the last row which
   3231          // gets the remainder
   3232          nscoord amountForRow =
   3233              (rowFrame == lastEligibleRow)
   3234                  ? aAmount - amountUsed
   3235                  : NSToCoordRound(((float)(bSizeToDistribute)) * ratio);
   3236          amountForRow = std::min(amountForRow, aAmount - amountUsed);
   3237 
   3238          if (bOriginRow != rowNormalRect.BStart(wm)) {
   3239            rowFrame->InvalidateFrameSubtree();
   3240          }
   3241 
   3242          // update the row bsize
   3243          nsRect origRowRect = rowFrame->GetRect();
   3244          nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
   3245          rowFrame->MovePositionBy(
   3246              wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm)));
   3247          rowFrame->SetSize(
   3248              wm, LogicalSize(wm, rowNormalRect.ISize(wm), newRowBSize));
   3249 
   3250          bOriginRow += newRowBSize + rowSpacing;
   3251          bEndRG += newRowBSize + rowSpacing;
   3252 
   3253          amountUsed += amountForRow;
   3254          amountUsedByRG += amountForRow;
   3255          NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation");
   3256          // rowFrame->DidResize();
   3257          nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect,
   3258                                             rowInkOverflow, false);
   3259        } else {
   3260          if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm)) {
   3261            rowFrame->InvalidateFrameSubtree();
   3262            rowFrame->MovePositionBy(
   3263                wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm)));
   3264            rowFrame->InvalidateFrameSubtree();
   3265          }
   3266          bOriginRow += rowNormalRect.BSize(wm) + rowSpacing;
   3267          bEndRG += rowNormalRect.BSize(wm) + rowSpacing;
   3268        }
   3269      }
   3270 
   3271      if (amountUsed > 0) {
   3272        if (rgNormalRect.BStart(wm) != bOriginRG) {
   3273          rgFrame->InvalidateFrameSubtree();
   3274        }
   3275 
   3276        nsRect origRgNormalRect = rgFrame->GetRect();
   3277        rgFrame->MovePositionBy(
   3278            wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
   3279        rgFrame->SetSize(wm,
   3280                         LogicalSize(wm, rgNormalRect.ISize(wm),
   3281                                     rgNormalRect.BSize(wm) + amountUsedByRG));
   3282 
   3283        nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
   3284                                           rgInkOverflow, false);
   3285      }
   3286 
   3287      // For vertical-rl mode, we needed to position the rows relative to the
   3288      // right-hand (block-start) side of the group; but we couldn't do that
   3289      // above, as we didn't know the rowGroupFrame's final block size yet.
   3290      // So we used a dummyContainerSize of 0,0 earlier, placing the rows to
   3291      // the left of the rowGroupFrame's (physical) origin. Now we move them
   3292      // all rightwards by its final width.
   3293      if (wm.IsVerticalRL()) {
   3294        nscoord rgWidth = rgFrame->GetSize().width;
   3295        for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
   3296             rowFrame = rowFrame->GetNextRow()) {
   3297          rowFrame->InvalidateFrameSubtree();
   3298          rowFrame->MovePositionBy(nsPoint(rgWidth, 0));
   3299          rowFrame->InvalidateFrameSubtree();
   3300        }
   3301      }
   3302    } else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
   3303      rgFrame->InvalidateFrameSubtree();
   3304      rgFrame->MovePositionBy(
   3305          wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
   3306      // Make sure child views are properly positioned
   3307      rgFrame->InvalidateFrameSubtree();
   3308    }
   3309    bOriginRG = bEndRG;
   3310  }
   3311 
   3312  ResizeCells(*this);
   3313 }
   3314 
   3315 nscoord nsTableFrame::GetColumnISizeFromFirstInFlow(int32_t aColIndex) {
   3316  MOZ_ASSERT(this == FirstInFlow());
   3317  nsTableColFrame* colFrame = GetColFrame(aColIndex);
   3318  return colFrame ? colFrame->GetFinalISize() : 0;
   3319 }
   3320 
   3321 nscoord nsTableFrame::GetColSpacing() {
   3322  if (IsBorderCollapse()) {
   3323    return 0;
   3324  }
   3325  return StyleTableBorder()->mBorderSpacing.width.ToAppUnits();
   3326 }
   3327 
   3328 // XXX: could cache this.  But be sure to check style changes if you do!
   3329 nscoord nsTableFrame::GetColSpacing(int32_t aColIndex) {
   3330  NS_ASSERTION(aColIndex >= -1 && aColIndex <= GetColCount(),
   3331               "Column index exceeds the bounds of the table");
   3332  // Index is irrelevant for ordinary tables.  We check that it falls within
   3333  // appropriate bounds to increase confidence of correctness in situations
   3334  // where it does matter.
   3335  return GetColSpacing();
   3336 }
   3337 
   3338 nscoord nsTableFrame::GetColSpacing(int32_t aStartColIndex,
   3339                                    int32_t aEndColIndex) {
   3340  NS_ASSERTION(aStartColIndex >= -1 && aStartColIndex <= GetColCount(),
   3341               "Start column index exceeds the bounds of the table");
   3342  NS_ASSERTION(aEndColIndex >= -1 && aEndColIndex <= GetColCount(),
   3343               "End column index exceeds the bounds of the table");
   3344  NS_ASSERTION(aStartColIndex <= aEndColIndex,
   3345               "End index must not be less than start index");
   3346  // Only one possible value so just multiply it out. Tables where index
   3347  // matters will override this function
   3348  return GetColSpacing() * (aEndColIndex - aStartColIndex);
   3349 }
   3350 
   3351 nscoord nsTableFrame::GetRowSpacing() {
   3352  if (IsBorderCollapse()) {
   3353    return 0;
   3354  }
   3355  return StyleTableBorder()->mBorderSpacing.height.ToAppUnits();
   3356 }
   3357 
   3358 // XXX: could cache this. But be sure to check style changes if you do!
   3359 nscoord nsTableFrame::GetRowSpacing(int32_t aRowIndex) {
   3360  NS_ASSERTION(aRowIndex >= -1 && aRowIndex <= GetRowCount(),
   3361               "Row index exceeds the bounds of the table");
   3362  // Index is irrelevant for ordinary tables.  We check that it falls within
   3363  // appropriate bounds to increase confidence of correctness in situations
   3364  // where it does matter.
   3365  return GetRowSpacing();
   3366 }
   3367 
   3368 nscoord nsTableFrame::GetRowSpacing(int32_t aStartRowIndex,
   3369                                    int32_t aEndRowIndex) {
   3370  NS_ASSERTION(aStartRowIndex >= -1 && aStartRowIndex <= GetRowCount(),
   3371               "Start row index exceeds the bounds of the table");
   3372  NS_ASSERTION(aEndRowIndex >= -1 && aEndRowIndex <= GetRowCount(),
   3373               "End row index exceeds the bounds of the table");
   3374  NS_ASSERTION(aStartRowIndex <= aEndRowIndex,
   3375               "End index must not be less than start index");
   3376  // Only one possible value so just multiply it out. Tables where index
   3377  // matters will override this function
   3378  return GetRowSpacing() * (aEndRowIndex - aStartRowIndex);
   3379 }
   3380 
   3381 nscoord nsTableFrame::SynthesizeFallbackBaseline(
   3382    mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
   3383  if (aBaselineGroup == BaselineSharingGroup::Last) {
   3384    return 0;
   3385  }
   3386  return BSize(aWM);
   3387 }
   3388 
   3389 /* virtual */
   3390 Maybe<nscoord> nsTableFrame::GetNaturalBaselineBOffset(
   3391    WritingMode aWM, BaselineSharingGroup aBaselineGroup,
   3392    BaselineExportContext) const {
   3393  if (StyleDisplay()->IsContainLayout()) {
   3394    return Nothing{};
   3395  }
   3396 
   3397  RowGroupArray orderedRowGroups = OrderedRowGroups();
   3398  // XXX not sure if this should be the size of the containing block instead.
   3399  nsSize containerSize = mRect.Size();
   3400  auto TableBaseline = [aWM, containerSize](
   3401                           nsTableRowGroupFrame* aRowGroup,
   3402                           nsTableRowFrame* aRow) -> Maybe<nscoord> {
   3403    const nscoord rgBStart =
   3404        aRowGroup->GetLogicalNormalRect(aWM, containerSize).BStart(aWM);
   3405    const nscoord rowBStart =
   3406        aRow->GetLogicalNormalRect(aWM, aRowGroup->GetSize()).BStart(aWM);
   3407    return aRow->GetRowBaseline(aWM).map(
   3408        [rgBStart, rowBStart](nscoord aBaseline) {
   3409          return rgBStart + rowBStart + aBaseline;
   3410        });
   3411  };
   3412  if (aBaselineGroup == BaselineSharingGroup::First) {
   3413    for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
   3414      nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
   3415      nsTableRowFrame* row = rgFrame->GetFirstRow();
   3416      if (row) {
   3417        return TableBaseline(rgFrame, row);
   3418      }
   3419    }
   3420  } else {
   3421    for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) {
   3422      nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
   3423      nsTableRowFrame* row = rgFrame->GetLastRow();
   3424      if (row) {
   3425        return TableBaseline(rgFrame, row).map([this, aWM](nscoord aBaseline) {
   3426          return BSize(aWM) - aBaseline;
   3427        });
   3428      }
   3429    }
   3430  }
   3431  return Nothing{};
   3432 }
   3433 
   3434 /* ----- global methods ----- */
   3435 
   3436 nsTableFrame* NS_NewTableFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
   3437  return new (aPresShell) nsTableFrame(aStyle, aPresShell->GetPresContext());
   3438 }
   3439 
   3440 NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame)
   3441 
   3442 nsTableFrame* nsTableFrame::GetTableFrame(nsIFrame* aFrame) {
   3443  for (nsIFrame* ancestor = aFrame->GetParent(); ancestor;
   3444       ancestor = ancestor->GetParent()) {
   3445    if (ancestor->IsTableFrame()) {
   3446      return static_cast<nsTableFrame*>(ancestor);
   3447    }
   3448  }
   3449  MOZ_CRASH("unable to find table parent");
   3450  return nullptr;
   3451 }
   3452 
   3453 bool nsTableFrame::IsAutoBSize(WritingMode aWM) {
   3454  const auto bsize =
   3455      StylePosition()->BSize(aWM, AnchorPosResolutionParams::From(this));
   3456  if (bsize->IsAuto()) {
   3457    return true;
   3458  }
   3459  return bsize->ConvertsToPercentage() && bsize->ToPercentage() <= 0.0f;
   3460 }
   3461 
   3462 nscoord nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aReflowInput,
   3463                                         const LogicalMargin& aBorderPadding,
   3464                                         nscoord aIntrinsicBorderBoxBSize) {
   3465  WritingMode wm = aReflowInput.GetWritingMode();
   3466  nscoord bSize = aReflowInput.ComputedBSize();
   3467  nscoord bp = aBorderPadding.BStartEnd(wm);
   3468  if (bSize == NS_UNCONSTRAINEDSIZE) {
   3469    if (aIntrinsicBorderBoxBSize == NS_UNCONSTRAINEDSIZE) {
   3470      return NS_UNCONSTRAINEDSIZE;
   3471    }
   3472    bSize = std::max(0, aIntrinsicBorderBoxBSize - bp);
   3473  }
   3474  return aReflowInput.ApplyMinMaxBSize(bSize) + bp;
   3475 }
   3476 
   3477 bool nsTableFrame::IsAutoLayout() {
   3478  if (StyleTable()->mLayoutStrategy == StyleTableLayout::Auto) {
   3479    return true;
   3480  }
   3481  // a fixed-layout inline-table must have a inline size
   3482  // and tables with inline size set to 'max-content' must be
   3483  // auto-layout (at least as long as
   3484  // FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX)
   3485  const auto iSize = StylePosition()->ISize(
   3486      GetWritingMode(), AnchorPosResolutionParams::From(this));
   3487  return iSize->IsAuto() || iSize->IsMaxContent();
   3488 }
   3489 
   3490 #ifdef DEBUG_FRAME_DUMP
   3491 nsresult nsTableFrame::GetFrameName(nsAString& aResult) const {
   3492  return MakeFrameName(u"Table"_ns, aResult);
   3493 }
   3494 #endif
   3495 
   3496 // Find the closet sibling before aPriorChildFrame (including aPriorChildFrame)
   3497 // that is of type aChildType
   3498 nsIFrame* nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame,
   3499                                           nsIFrame* aPriorChildFrame,
   3500                                           LayoutFrameType aChildType) {
   3501  nsIFrame* result = nullptr;
   3502  if (!aPriorChildFrame) {
   3503    return result;
   3504  }
   3505  if (aChildType == aPriorChildFrame->Type()) {
   3506    return aPriorChildFrame;
   3507  }
   3508 
   3509  // aPriorChildFrame is not of type aChildType, so we need start from
   3510  // the beginnng and find the closest one
   3511  nsIFrame* lastMatchingFrame = nullptr;
   3512  nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild();
   3513  while (childFrame && (childFrame != aPriorChildFrame)) {
   3514    if (aChildType == childFrame->Type()) {
   3515      lastMatchingFrame = childFrame;
   3516    }
   3517    childFrame = childFrame->GetNextSibling();
   3518  }
   3519  return lastMatchingFrame;
   3520 }
   3521 
   3522 #ifdef DEBUG
   3523 void nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame) {
   3524  if (!aKidFrame) return;
   3525 
   3526  for (nsIFrame* cFrame : aKidFrame->PrincipalChildList()) {
   3527    nsTableRowFrame* rowFrame = do_QueryFrame(cFrame);
   3528    if (rowFrame) {
   3529      printf("row(%d)=%p ", rowFrame->GetRowIndex(),
   3530             static_cast<void*>(rowFrame));
   3531      for (nsIFrame* childFrame : cFrame->PrincipalChildList()) {
   3532        nsTableCellFrame* cellFrame = do_QueryFrame(childFrame);
   3533        if (cellFrame) {
   3534          uint32_t colIndex = cellFrame->ColIndex();
   3535          printf("cell(%u)=%p ", colIndex, static_cast<void*>(childFrame));
   3536        }
   3537      }
   3538      printf("\n");
   3539    } else {
   3540      DumpRowGroup(rowFrame);
   3541    }
   3542  }
   3543 }
   3544 
   3545 void nsTableFrame::Dump(bool aDumpRows, bool aDumpCols, bool aDumpCellMap) {
   3546  printf("***START TABLE DUMP*** \n");
   3547  // dump the columns widths array
   3548  printf("mColWidths=");
   3549  int32_t numCols = GetColCount();
   3550  int32_t colIdx;
   3551  nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
   3552  for (colIdx = 0; colIdx < numCols; colIdx++) {
   3553    printf("%d ", fif->GetColumnISizeFromFirstInFlow(colIdx));
   3554  }
   3555  printf("\n");
   3556 
   3557  if (aDumpRows) {
   3558    nsIFrame* kidFrame = mFrames.FirstChild();
   3559    while (kidFrame) {
   3560      DumpRowGroup(kidFrame);
   3561      kidFrame = kidFrame->GetNextSibling();
   3562    }
   3563  }
   3564 
   3565  if (aDumpCols) {
   3566    // output col frame cache
   3567    printf("\n col frame cache ->");
   3568    for (colIdx = 0; colIdx < numCols; colIdx++) {
   3569      nsTableColFrame* colFrame = mColFrames.ElementAt(colIdx);
   3570      if (0 == (colIdx % 8)) {
   3571        printf("\n");
   3572      }
   3573      printf("%d=%p ", colIdx, static_cast<void*>(colFrame));
   3574      nsTableColType colType = colFrame->GetColType();
   3575      switch (colType) {
   3576        case eColContent:
   3577          printf(" content ");
   3578          break;
   3579        case eColAnonymousCol:
   3580          printf(" anonymous-column ");
   3581          break;
   3582        case eColAnonymousColGroup:
   3583          printf(" anonymous-colgroup ");
   3584          break;
   3585        case eColAnonymousCell:
   3586          printf(" anonymous-cell ");
   3587          break;
   3588      }
   3589    }
   3590    printf("\n colgroups->");
   3591    for (nsIFrame* childFrame : mColGroups) {
   3592      if (LayoutFrameType::TableColGroup == childFrame->Type()) {
   3593        nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)childFrame;
   3594        colGroupFrame->Dump(1);
   3595      }
   3596    }
   3597    for (colIdx = 0; colIdx < numCols; colIdx++) {
   3598      printf("\n");
   3599      nsTableColFrame* colFrame = GetColFrame(colIdx);
   3600      colFrame->Dump(1);
   3601    }
   3602  }
   3603  if (aDumpCellMap) {
   3604    nsTableCellMap* cellMap = GetCellMap();
   3605    cellMap->Dump();
   3606  }
   3607  printf(" ***END TABLE DUMP*** \n");
   3608 }
   3609 #endif
   3610 
   3611 bool nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const {
   3612  if (aColIndex == 0) {
   3613    return true;
   3614  }
   3615  // Since fixed-layout tables should not have their column sizes change
   3616  // as they load, we assume that all columns are significant.
   3617  auto* fif = static_cast<nsTableFrame*>(FirstInFlow());
   3618  if (fif->LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed) {
   3619    return true;
   3620  }
   3621  nsTableCellMap* cellMap = fif->GetCellMap();
   3622  if (!cellMap) {
   3623    return false;
   3624  }
   3625  if (cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0) {
   3626    return true;
   3627  }
   3628  // Check if we have a <col> element with a non-zero definite inline size.
   3629  // Note: percentages and calc(%) are intentionally not considered.
   3630  if (const auto* col = fif->GetColFrame(aColIndex)) {
   3631    const auto anchorResolutionParams = AnchorPosResolutionParams::From(col);
   3632    const auto iSize =
   3633        col->StylePosition()->ISize(GetWritingMode(), anchorResolutionParams);
   3634    if (iSize->ConvertsToLength() && iSize->ToLength() > 0) {
   3635      const auto maxISize = col->StylePosition()->MaxISize(
   3636          GetWritingMode(), anchorResolutionParams);
   3637      if (!maxISize->ConvertsToLength() || maxISize->ToLength() > 0) {
   3638        return true;
   3639      }
   3640    }
   3641    const auto minISize = col->StylePosition()->MinISize(
   3642        GetWritingMode(), anchorResolutionParams);
   3643    if (minISize->ConvertsToLength() && minISize->ToLength() > 0) {
   3644      return true;
   3645    }
   3646  }
   3647  return false;
   3648 }
   3649 
   3650 /********************************************************************************
   3651 * Collapsing Borders
   3652 *
   3653 *  The CSS spec says to resolve border conflicts in this order:
   3654 *  1) any border with the style HIDDEN wins
   3655 *  2) the widest border with a style that is not NONE wins
   3656 *  3) the border styles are ranked in this order, highest to lowest precedence:
   3657 *     double, solid, dashed, dotted, ridge, outset, groove, inset
   3658 *  4) borders that are of equal width and style (differ only in color) have
   3659 *     this precedence: cell, row, rowgroup, col, colgroup, table
   3660 *  5) if all border styles are NONE, then that's the computed border style.
   3661 *******************************************************************************/
   3662 
   3663 #ifdef DEBUG
   3664 #  define VerifyNonNegativeDamageRect(r)                       \
   3665    NS_ASSERTION((r).StartCol() >= 0, "negative col index");   \
   3666    NS_ASSERTION((r).StartRow() >= 0, "negative row index");   \
   3667    NS_ASSERTION((r).ColCount() >= 0, "negative cols damage"); \
   3668    NS_ASSERTION((r).RowCount() >= 0, "negative rows damage");
   3669 #  define VerifyDamageRect(r)                          \
   3670    VerifyNonNegativeDamageRect(r);                    \
   3671    NS_ASSERTION((r).EndCol() <= GetColCount(),        \
   3672                 "cols damage extends outside table"); \
   3673    NS_ASSERTION((r).EndRow() <= GetRowCount(),        \
   3674                 "rows damage extends outside table");
   3675 #endif
   3676 
   3677 void nsTableFrame::AddBCDamageArea(const TableArea& aValue) {
   3678  MOZ_ASSERT(IsBorderCollapse(),
   3679             "Why call this if we are not border-collapsed?");
   3680 #ifdef DEBUG
   3681  VerifyDamageRect(aValue);
   3682 #endif
   3683 
   3684  SetNeedToCalcBCBorders(true);
   3685  SetNeedToCalcHasBCBorders(true);
   3686  // Get the property
   3687  TableBCData* value = GetOrCreateTableBCData();
   3688 
   3689 #ifdef DEBUG
   3690  VerifyNonNegativeDamageRect(value->mDamageArea);
   3691 #endif
   3692  // Clamp the old damage area to the current table area in case it shrunk.
   3693  int32_t cols = GetColCount();
   3694  if (value->mDamageArea.EndCol() > cols) {
   3695    if (value->mDamageArea.StartCol() > cols) {
   3696      value->mDamageArea.StartCol() = cols;
   3697      value->mDamageArea.ColCount() = 0;
   3698    } else {
   3699      value->mDamageArea.ColCount() = cols - value->mDamageArea.StartCol();
   3700    }
   3701  }
   3702  int32_t rows = GetRowCount();
   3703  if (value->mDamageArea.EndRow() > rows) {
   3704    if (value->mDamageArea.StartRow() > rows) {
   3705      value->mDamageArea.StartRow() = rows;
   3706      value->mDamageArea.RowCount() = 0;
   3707    } else {
   3708      value->mDamageArea.RowCount() = rows - value->mDamageArea.StartRow();
   3709    }
   3710  }
   3711 
   3712  // Construct a union of the new and old damage areas.
   3713  value->mDamageArea.UnionArea(value->mDamageArea, aValue);
   3714 }
   3715 
   3716 void nsTableFrame::SetFullBCDamageArea() {
   3717  MOZ_ASSERT(IsBorderCollapse(),
   3718             "Why call this if we are not border-collapsed?");
   3719 
   3720  SetNeedToCalcBCBorders(true);
   3721  SetNeedToCalcHasBCBorders(true);
   3722 
   3723  TableBCData* value = GetOrCreateTableBCData();
   3724  value->mDamageArea = TableArea(0, 0, GetColCount(), GetRowCount());
   3725 }
   3726 
   3727 /* BCCellBorder represents a border segment which can be either an inline-dir
   3728 * or a block-dir segment. For each segment we need to know the color, width,
   3729 * style, who owns it and how long it is in cellmap coordinates.
   3730 * Ownership of these segments is important to calculate which corners should
   3731 * be bevelled. This structure has dual use, its used first to compute the
   3732 * dominant border for inline-dir and block-dir segments and to store the
   3733 * preliminary computed border results in the BCCellBorders structure.
   3734 * This temporary storage is not symmetric with respect to inline-dir and
   3735 * block-dir border segments, its always column oriented. For each column in
   3736 * the cellmap there is a temporary stored block-dir and inline-dir segment.
   3737 * XXX_Bernd this asymmetry is the root of those rowspan bc border errors
   3738 */
   3739 struct BCCellBorder {
   3740  BCCellBorder() { Reset(0, 1); }
   3741  void Reset(uint32_t aRowIndex, uint32_t aRowSpan);
   3742  nscolor color;           // border segment color
   3743  nscoord width;           // border segment width
   3744  StyleBorderStyle style;  // border segment style, possible values are defined
   3745                           // in nsStyleConsts.h as StyleBorderStyle::*
   3746  BCBorderOwner owner;     // border segment owner, possible values are defined
   3747                           // in celldata.h. In the cellmap for each border
   3748                           // segment we store the owner and later when
   3749                           // painting we know the owner and can retrieve the
   3750                           // style info from the corresponding frame
   3751  int32_t rowIndex;        // rowIndex of temporary stored inline-dir border
   3752                           // segments relative to the table
   3753  int32_t rowSpan;         // row span of temporary stored inline-dir border
   3754                           // segments
   3755 };
   3756 
   3757 void BCCellBorder::Reset(uint32_t aRowIndex, uint32_t aRowSpan) {
   3758  style = StyleBorderStyle::None;
   3759  color = 0;
   3760  width = 0;
   3761  owner = eTableOwner;
   3762  rowIndex = aRowIndex;
   3763  rowSpan = aRowSpan;
   3764 }
   3765 
   3766 class BCMapCellIterator;
   3767 
   3768 /*****************************************************************
   3769 *  BCMapCellInfo
   3770 * This structure stores information during the computation of winning borders
   3771 * in CalcBCBorders, so that they don't need to be looked up repeatedly.
   3772 ****************************************************************/
   3773 struct BCMapCellInfo final {
   3774  explicit BCMapCellInfo(nsTableFrame* aTableFrame);
   3775  void ResetCellInfo();
   3776  void SetInfo(nsTableRowFrame* aNewRow, int32_t aColIndex,
   3777               BCCellData* aCellData, BCMapCellIterator* aIter,
   3778               nsCellMap* aCellMap = nullptr);
   3779 
   3780  // Functions to (re)set the border widths on the table related cell frames,
   3781  // where the knowledge about the current position in the table is used.
   3782  // For most "normal" cells that have row/colspan of 1, these functions
   3783  // are called once at most during the reflow, setting the value as given
   3784  // (Discarding the value from the previous reflow, which is now irrelevant).
   3785  // However, for cells spanning multiple rows/coluns, the maximum border
   3786  // width seen is stored. This is controlled by calling the reset functions
   3787  // before the cell's border is computed the first time.
   3788  void ResetIStartBorderWidths();
   3789  void ResetIEndBorderWidths();
   3790  void ResetBStartBorderWidths();
   3791  void ResetBEndBorderWidths();
   3792 
   3793  void SetIStartBorderWidths(nscoord aWidth);
   3794  void SetIEndBorderWidths(nscoord aWidth);
   3795  void SetBStartBorderWidths(nscoord aWidth);
   3796  void SetBEndBorderWidths(nscoord aWidth);
   3797 
   3798  // functions to compute the borders; they depend on the
   3799  // knowledge about the current position in the table. The edge functions
   3800  // should be called if a table edge is involved, otherwise the internal
   3801  // functions should be called.
   3802  BCCellBorder GetBStartEdgeBorder();
   3803  BCCellBorder GetBEndEdgeBorder();
   3804  BCCellBorder GetIStartEdgeBorder();
   3805  BCCellBorder GetIEndEdgeBorder();
   3806  BCCellBorder GetIEndInternalBorder();
   3807  BCCellBorder GetIStartInternalBorder();
   3808  BCCellBorder GetBStartInternalBorder();
   3809  BCCellBorder GetBEndInternalBorder();
   3810 
   3811  // functions to set the internal position information
   3812  void SetColumn(int32_t aColX);
   3813  // Increment the row as we loop over the rows of a rowspan
   3814  void IncrementRow(bool aResetToBStartRowOfCell = false);
   3815 
   3816  // Helper functions to get extent of the cell
   3817  int32_t GetCellEndRowIndex() const;
   3818  int32_t GetCellEndColIndex() const;
   3819 
   3820  // Storage of table information required to compute individual cell
   3821  // information.
   3822  nsTableFrame* mTableFrame;
   3823  nsTableFrame* mTableFirstInFlow;
   3824  int32_t mNumTableRows;
   3825  int32_t mNumTableCols;
   3826  WritingMode mTableWM;
   3827 
   3828  // a cell can only belong to one rowgroup
   3829  nsTableRowGroupFrame* mRowGroup;
   3830 
   3831  // a cell with a rowspan has a bstart and a bend row, and rows in between
   3832  nsTableRowFrame* mStartRow;
   3833  nsTableRowFrame* mEndRow;
   3834  nsTableRowFrame* mCurrentRowFrame;
   3835 
   3836  // a cell with a colspan has an istart and iend column and columns in between
   3837  // they can belong to different colgroups
   3838  nsTableColGroupFrame* mColGroup;
   3839  nsTableColGroupFrame* mCurrentColGroupFrame;
   3840 
   3841  nsTableColFrame* mStartCol;
   3842  nsTableColFrame* mEndCol;
   3843  nsTableColFrame* mCurrentColFrame;
   3844 
   3845  // cell information
   3846  BCCellData* mCellData;
   3847  nsBCTableCellFrame* mCell;
   3848 
   3849  int32_t mRowIndex;
   3850  int32_t mRowSpan;
   3851  int32_t mColIndex;
   3852  int32_t mColSpan;
   3853 
   3854  // flags to describe the position of the cell with respect to the row- and
   3855  // colgroups, for instance mRgAtStart documents that the bStart cell border
   3856  // hits a rowgroup border
   3857  bool mRgAtStart;
   3858  bool mRgAtEnd;
   3859  bool mCgAtStart;
   3860  bool mCgAtEnd;
   3861 };
   3862 
   3863 BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame)
   3864    : mTableFrame(aTableFrame),
   3865      mTableFirstInFlow(static_cast<nsTableFrame*>(aTableFrame->FirstInFlow())),
   3866      mNumTableRows(aTableFrame->GetRowCount()),
   3867      mNumTableCols(aTableFrame->GetColCount()),
   3868      mTableWM(aTableFrame->Style()),
   3869      mCurrentRowFrame(nullptr),
   3870      mCurrentColGroupFrame(nullptr),
   3871      mCurrentColFrame(nullptr) {
   3872  ResetCellInfo();
   3873 }
   3874 
   3875 void BCMapCellInfo::ResetCellInfo() {
   3876  mCellData = nullptr;
   3877  mRowGroup = nullptr;
   3878  mStartRow = nullptr;
   3879  mEndRow = nullptr;
   3880  mColGroup = nullptr;
   3881  mStartCol = nullptr;
   3882  mEndCol = nullptr;
   3883  mCell = nullptr;
   3884  mRowIndex = mRowSpan = mColIndex = mColSpan = 0;
   3885  mRgAtStart = mRgAtEnd = mCgAtStart = mCgAtEnd = false;
   3886 }
   3887 
   3888 inline int32_t BCMapCellInfo::GetCellEndRowIndex() const {
   3889  return mRowIndex + mRowSpan - 1;
   3890 }
   3891 
   3892 inline int32_t BCMapCellInfo::GetCellEndColIndex() const {
   3893  return mColIndex + mColSpan - 1;
   3894 }
   3895 
   3896 static TableBCData* GetTableBCData(nsTableFrame* aTableFrame) {
   3897  auto* firstInFlow = static_cast<nsTableFrame*>(aTableFrame->FirstInFlow());
   3898  return firstInFlow->GetTableBCData();
   3899 }
   3900 
   3901 /*****************************************************************
   3902 *  BCMapTableInfo
   3903 * This structure stores controls border information global to the
   3904 * table computed during the border-collapsed border calcuation.
   3905 ****************************************************************/
   3906 struct BCMapTableInfo final {
   3907  explicit BCMapTableInfo(nsTableFrame* aTableFrame)
   3908      : mTableBCData{GetTableBCData(aTableFrame)} {}
   3909 
   3910  void ResetTableIStartBorderWidth() { mTableBCData->mIStartBorderWidth = 0; }
   3911 
   3912  void ResetTableIEndBorderWidth() { mTableBCData->mIEndBorderWidth = 0; }
   3913 
   3914  void ResetTableBStartBorderWidth() { mTableBCData->mBStartBorderWidth = 0; }
   3915 
   3916  void ResetTableBEndBorderWidth() { mTableBCData->mBEndBorderWidth = 0; }
   3917 
   3918  void SetTableIStartBorderWidth(nscoord aWidth);
   3919  void SetTableIEndBorderWidth(nscoord aWidth);
   3920  void SetTableBStartBorderWidth(nscoord aWidth);
   3921  void SetTableBEndBorderWidth(nscoord aWidth);
   3922 
   3923  TableBCData* mTableBCData;
   3924 };
   3925 
   3926 class BCMapCellIterator {
   3927 public:
   3928  BCMapCellIterator(nsTableFrame* aTableFrame, const TableArea& aDamageArea);
   3929 
   3930  void First(BCMapCellInfo& aMapInfo);
   3931 
   3932  void Next(BCMapCellInfo& aMapInfo);
   3933 
   3934  void PeekIEnd(const BCMapCellInfo& aRefInfo, int32_t aRowIndex,
   3935                BCMapCellInfo& aAjaInfo);
   3936 
   3937  void PeekBEnd(const BCMapCellInfo& aRefInfo, int32_t aColIndex,
   3938                BCMapCellInfo& aAjaInfo);
   3939 
   3940  void PeekIStart(const BCMapCellInfo& aRefInfo, int32_t aRowIndex,
   3941                  BCMapCellInfo& aAjaInfo);
   3942 
   3943  bool IsNewRow() { return mIsNewRow; }
   3944 
   3945  nsTableRowFrame* GetPrevRow() const { return mPrevRow; }
   3946  nsTableRowFrame* GetCurrentRow() const { return mRow; }
   3947  nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup; }
   3948 
   3949  int32_t mRowGroupStart;
   3950  int32_t mRowGroupEnd;
   3951  bool mAtEnd;
   3952  nsCellMap* mCellMap;
   3953 
   3954 private:
   3955  bool SetNewRow(nsTableRowFrame* row = nullptr);
   3956  bool SetNewRowGroup(bool aFindFirstDamagedRow);
   3957  void PeekIAt(const BCMapCellInfo& aRefInfo, int32_t aRowIndex,
   3958               int32_t aColIndex, BCMapCellInfo& aAjaInfo);
   3959 
   3960  nsTableFrame* mTableFrame;
   3961  nsTableCellMap* mTableCellMap;
   3962  nsTableFrame::RowGroupArray mRowGroups;
   3963  nsTableRowGroupFrame* mRowGroup;
   3964  int32_t mRowGroupIndex;
   3965  uint32_t mNumTableRows;
   3966  nsTableRowFrame* mRow;
   3967  nsTableRowFrame* mPrevRow;
   3968  bool mIsNewRow;
   3969  int32_t mRowIndex;
   3970  uint32_t mNumTableCols;
   3971  int32_t mColIndex;
   3972  // We don't necessarily want to traverse all areas
   3973  // of the table - mArea(Start|End) specify the area to traverse.
   3974  // TODO(dshin): Should be not abuse `nsPoint` for this - See bug 1879847.
   3975  nsPoint mAreaStart;
   3976  nsPoint mAreaEnd;
   3977 };
   3978 
   3979 BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame,
   3980                                     const TableArea& aDamageArea)
   3981    : mRowGroupStart(0),
   3982      mRowGroupEnd(0),
   3983      mCellMap(nullptr),
   3984      mTableFrame(aTableFrame),
   3985      mRowGroups(aTableFrame->OrderedRowGroups()),
   3986      mRowGroup(nullptr),
   3987      mPrevRow(nullptr),
   3988      mIsNewRow(false) {
   3989  mTableCellMap = aTableFrame->GetCellMap();
   3990 
   3991  mAreaStart.x = aDamageArea.StartCol();
   3992  mAreaStart.y = aDamageArea.StartRow();
   3993  mAreaEnd.x = aDamageArea.EndCol() - 1;
   3994  mAreaEnd.y = aDamageArea.EndRow() - 1;
   3995 
   3996  mNumTableRows = mTableFrame->GetRowCount();
   3997  mRow = nullptr;
   3998  mRowIndex = 0;
   3999  mNumTableCols = mTableFrame->GetColCount();
   4000  mColIndex = 0;
   4001  mRowGroupIndex = -1;
   4002 
   4003  mAtEnd = true;  // gets reset when First() is called
   4004 }
   4005 
   4006 // fill fields that we need for border collapse computation on a given cell
   4007 void BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow, int32_t aColIndex,
   4008                            BCCellData* aCellData, BCMapCellIterator* aIter,
   4009                            nsCellMap* aCellMap) {
   4010  // fill the cell information
   4011  mCellData = aCellData;
   4012  mColIndex = aColIndex;
   4013 
   4014  // initialize the row information if it was not previously set for cells in
   4015  // this row
   4016  mRowIndex = 0;
   4017  if (aNewRow) {
   4018    mStartRow = aNewRow;
   4019    mRowIndex = aNewRow->GetRowIndex();
   4020  }
   4021 
   4022  // fill cell frame info and row information
   4023  mCell = nullptr;
   4024  mRowSpan = 1;
   4025  mColSpan = 1;
   4026  if (aCellData) {
   4027    mCell = static_cast<nsBCTableCellFrame*>(aCellData->GetCellFrame());
   4028    if (mCell) {
   4029      if (!mStartRow) {
   4030        mStartRow = mCell->GetTableRowFrame();
   4031        if (!mStartRow) ABORT0();
   4032        mRowIndex = mStartRow->GetRowIndex();
   4033      }
   4034      mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap);
   4035      mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap);
   4036    }
   4037  }
   4038 
   4039  if (!mStartRow) {
   4040    mStartRow = aIter->GetCurrentRow();
   4041  }
   4042  if (1 == mRowSpan) {
   4043    mEndRow = mStartRow;
   4044  } else {
   4045    mEndRow = mStartRow->GetNextRow();
   4046    if (mEndRow) {
   4047      for (int32_t span = 2; mEndRow && span < mRowSpan; span++) {
   4048        mEndRow = mEndRow->GetNextRow();
   4049      }
   4050      NS_ASSERTION(mEndRow, "spanned row not found");
   4051    } else {
   4052      NS_ERROR("error in cell map");
   4053      mRowSpan = 1;
   4054      mEndRow = mStartRow;
   4055    }
   4056  }
   4057  // row group frame info
   4058  // try to reuse the rgStart and rgEnd from the iterator as calls to
   4059  // GetRowCount() are computationally expensive and should be avoided if
   4060  // possible
   4061  uint32_t rgStart = aIter->mRowGroupStart;
   4062  uint32_t rgEnd = aIter->mRowGroupEnd;
   4063  mRowGroup = mStartRow->GetTableRowGroupFrame();
   4064  if (mRowGroup != aIter->GetCurrentRowGroup()) {
   4065    rgStart = mRowGroup->GetStartRowIndex();
   4066    rgEnd = rgStart + mRowGroup->GetRowCount() - 1;
   4067  }
   4068  uint32_t rowIndex = mStartRow->GetRowIndex();
   4069  mRgAtStart = rgStart == rowIndex;
   4070  mRgAtEnd = rgEnd == rowIndex + mRowSpan - 1;
   4071 
   4072  // col frame info
   4073  mStartCol = mTableFirstInFlow->GetColFrame(aColIndex);
   4074  if (!mStartCol) ABORT0();
   4075 
   4076  mEndCol = mStartCol;
   4077  if (mColSpan > 1) {
   4078    nsTableColFrame* colFrame =
   4079        mTableFirstInFlow->GetColFrame(aColIndex + mColSpan - 1);
   4080    if (!colFrame) ABORT0();
   4081    mEndCol = colFrame;
   4082  }
   4083 
   4084  // col group frame info
   4085  mColGroup = mStartCol->GetTableColGroupFrame();
   4086  int32_t cgStart = mColGroup->GetStartColumnIndex();
   4087  int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1);
   4088  mCgAtStart = cgStart == aColIndex;
   4089  mCgAtEnd = cgEnd == aColIndex + mColSpan - 1;
   4090 }
   4091 
   4092 bool BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow) {
   4093  mAtEnd = true;
   4094  mPrevRow = mRow;
   4095  if (aRow) {
   4096    mRow = aRow;
   4097  } else if (mRow) {
   4098    mRow = mRow->GetNextRow();
   4099  }
   4100  if (mRow) {
   4101    mRowIndex = mRow->GetRowIndex();
   4102    // get to the first entry with an originating cell
   4103    int32_t rgRowIndex = mRowIndex - mRowGroupStart;
   4104    if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length()) ABORT1(false);
   4105    const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex];
   4106 
   4107    for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) {
   4108      CellData* cellData = row.SafeElementAt(mColIndex);
   4109      if (!cellData) {  // add a dead cell data
   4110        TableArea damageArea;
   4111        cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex,
   4112                                        false, 0, damageArea);
   4113        if (!cellData) ABORT1(false);
   4114      }
   4115      if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
   4116        break;
   4117      }
   4118    }
   4119    mIsNewRow = true;
   4120    mAtEnd = false;
   4121  } else
   4122    ABORT1(false);
   4123 
   4124  return !mAtEnd;
   4125 }
   4126 
   4127 bool BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow) {
   4128  mAtEnd = true;
   4129  int32_t numRowGroups = mRowGroups.Length();
   4130  mCellMap = nullptr;
   4131  for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) {
   4132    mRowGroup = mRowGroups[mRowGroupIndex];
   4133    int32_t rowCount = mRowGroup->GetRowCount();
   4134    mRowGroupStart = mRowGroup->GetStartRowIndex();
   4135    mRowGroupEnd = mRowGroupStart + rowCount - 1;
   4136    if (rowCount > 0) {
   4137      mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap);
   4138      if (!mCellMap) ABORT1(false);
   4139      nsTableRowFrame* firstRow = mRowGroup->GetFirstRow();
   4140      if (aFindFirstDamagedRow) {
   4141        if ((mAreaStart.y >= mRowGroupStart) &&
   4142            (mAreaStart.y <= mRowGroupEnd)) {
   4143          // the damage area starts in the row group
   4144 
   4145          // find the correct first damaged row
   4146          int32_t numRows = mAreaStart.y - mRowGroupStart;
   4147          for (int32_t i = 0; i < numRows; i++) {
   4148            firstRow = firstRow->GetNextRow();
   4149            if (!firstRow) ABORT1(false);
   4150          }
   4151 
   4152        } else {
   4153          continue;
   4154        }
   4155      }
   4156      if (SetNewRow(firstRow)) {  // sets mAtEnd
   4157        break;
   4158      }
   4159    }
   4160  }
   4161 
   4162  return !mAtEnd;
   4163 }
   4164 
   4165 void BCMapCellIterator::First(BCMapCellInfo& aMapInfo) {
   4166  aMapInfo.ResetCellInfo();
   4167 
   4168  SetNewRowGroup(true);  // sets mAtEnd
   4169  while (!mAtEnd) {
   4170    if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) {
   4171      BCCellData* cellData = static_cast<BCCellData*>(
   4172          mCellMap->GetDataAt(mAreaStart.y - mRowGroupStart, mAreaStart.x));
   4173      if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
   4174        aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this);
   4175        return;
   4176      } else {
   4177        NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)),
   4178                     "damage area expanded incorrectly");
   4179      }
   4180    }
   4181    SetNewRowGroup(true);  // sets mAtEnd
   4182  }
   4183 }
   4184 
   4185 void BCMapCellIterator::Next(BCMapCellInfo& aMapInfo) {
   4186  if (mAtEnd) ABORT0();
   4187  aMapInfo.ResetCellInfo();
   4188 
   4189  mIsNewRow = false;
   4190  mColIndex++;
   4191  while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) {
   4192    for (; mColIndex <= mAreaEnd.x; mColIndex++) {
   4193      int32_t rgRowIndex = mRowIndex - mRowGroupStart;
   4194      BCCellData* cellData =
   4195          static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, mColIndex));
   4196      if (!cellData) {  // add a dead cell data
   4197        TableArea damageArea;
   4198        cellData = static_cast<BCCellData*>(mCellMap->AppendCell(
   4199            *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
   4200        if (!cellData) ABORT0();
   4201      }
   4202      if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
   4203        aMapInfo.SetInfo(mRow, mColIndex, cellData, this);
   4204        return;
   4205      }
   4206    }
   4207    if (mRowIndex >= mRowGroupEnd) {
   4208      SetNewRowGroup(false);  // could set mAtEnd
   4209    } else {
   4210      SetNewRow();  // could set mAtEnd
   4211    }
   4212  }
   4213  mAtEnd = true;
   4214 }
   4215 
   4216 void BCMapCellIterator::PeekIEnd(const BCMapCellInfo& aRefInfo,
   4217                                 int32_t aRowIndex, BCMapCellInfo& aAjaInfo) {
   4218  PeekIAt(aRefInfo, aRowIndex, aRefInfo.mColIndex + aRefInfo.mColSpan,
   4219          aAjaInfo);
   4220 }
   4221 
   4222 void BCMapCellIterator::PeekBEnd(const BCMapCellInfo& aRefInfo,
   4223                                 int32_t aColIndex, BCMapCellInfo& aAjaInfo) {
   4224  aAjaInfo.ResetCellInfo();
   4225  int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan;
   4226  int32_t rgRowIndex = rowIndex - mRowGroupStart;
   4227  nsTableRowGroupFrame* rg = mRowGroup;
   4228  nsCellMap* cellMap = mCellMap;
   4229  nsTableRowFrame* nextRow = nullptr;
   4230  if (rowIndex > mRowGroupEnd) {
   4231    int32_t nextRgIndex = mRowGroupIndex;
   4232    do {
   4233      nextRgIndex++;
   4234      rg = mRowGroups.SafeElementAt(nextRgIndex);
   4235      if (rg) {
   4236        cellMap = mTableCellMap->GetMapFor(rg, cellMap);
   4237        if (!cellMap) ABORT0();
   4238        // First row of the next row group
   4239        rgRowIndex = 0;
   4240        nextRow = rg->GetFirstRow();
   4241      }
   4242    } while (rg && !nextRow);
   4243    if (!rg) {
   4244      return;
   4245    }
   4246  } else {
   4247    // get the row within the same row group
   4248    nextRow = mRow;
   4249    for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) {
   4250      nextRow = nextRow->GetNextRow();
   4251      if (!nextRow) ABORT0();
   4252    }
   4253  }
   4254 
   4255  BCCellData* cellData =
   4256      static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
   4257  if (!cellData) {  // add a dead cell data
   4258    NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error");
   4259    TableArea damageArea;
   4260    cellData = static_cast<BCCellData*>(cellMap->AppendCell(
   4261        *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
   4262    if (!cellData) ABORT0();
   4263  }
   4264  if (cellData->IsColSpan()) {
   4265    aColIndex -= static_cast<int32_t>(cellData->GetColSpanOffset());
   4266    cellData =
   4267        static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
   4268  }
   4269  aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap);
   4270 }
   4271 
   4272 void BCMapCellIterator::PeekIStart(const BCMapCellInfo& aRefInfo,
   4273                                   int32_t aRowIndex, BCMapCellInfo& aAjaInfo) {
   4274  NS_ASSERTION(aRefInfo.mColIndex != 0, "program error");
   4275  PeekIAt(aRefInfo, aRowIndex, aRefInfo.mColIndex - 1, aAjaInfo);
   4276 }
   4277 
   4278 void BCMapCellIterator::PeekIAt(const BCMapCellInfo& aRefInfo,
   4279                                int32_t aRowIndex, int32_t aColIndex,
   4280                                BCMapCellInfo& aAjaInfo) {
   4281  aAjaInfo.ResetCellInfo();
   4282  int32_t rgRowIndex = aRowIndex - mRowGroupStart;
   4283 
   4284  auto* cellData =
   4285      static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, aColIndex));
   4286  if (!cellData) {  // add a dead cell data
   4287    NS_ASSERTION(aColIndex < mTableCellMap->GetColCount(), "program error");
   4288    TableArea damageArea;
   4289    cellData = static_cast<BCCellData*>(mCellMap->AppendCell(
   4290        *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
   4291    if (!cellData) ABORT0();
   4292  }
   4293  nsTableRowFrame* row = nullptr;
   4294  if (cellData->IsRowSpan()) {
   4295    rgRowIndex -= static_cast<int32_t>(cellData->GetRowSpanOffset());
   4296    cellData =
   4297        static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, aColIndex));
   4298    if (!cellData) ABORT0();
   4299  } else {
   4300    row = mRow;
   4301  }
   4302  aAjaInfo.SetInfo(row, aColIndex, cellData, this);
   4303 }
   4304 
   4305 #define CELL_CORNER true
   4306 
   4307 /** return the border style, border color and optionally the width for a given
   4308 * frame and side
   4309 * @param aFrame           - query the info for this frame
   4310 * @param aTableWM         - the writing-mode of the frame
   4311 * @param aSide            - the side of the frame
   4312 * @param aStyle           - the border style
   4313 * @param aColor           - the border color
   4314 * @param aWidth           - the border width
   4315 */
   4316 static void GetColorAndStyle(const nsIFrame* aFrame, WritingMode aTableWM,
   4317                             LogicalSide aSide, StyleBorderStyle* aStyle,
   4318                             nscolor* aColor, nscoord* aWidth = nullptr) {
   4319  MOZ_ASSERT(aFrame, "null frame");
   4320  MOZ_ASSERT(aStyle && aColor, "null argument");
   4321 
   4322  // initialize out arg
   4323  *aColor = 0;
   4324  if (aWidth) {
   4325    *aWidth = 0;
   4326  }
   4327 
   4328  const nsStyleBorder* styleData = aFrame->StyleBorder();
   4329  mozilla::Side physicalSide = aTableWM.PhysicalSide(aSide);
   4330  *aStyle = styleData->GetBorderStyle(physicalSide);
   4331 
   4332  if ((StyleBorderStyle::None == *aStyle) ||
   4333      (StyleBorderStyle::Hidden == *aStyle)) {
   4334    return;
   4335  }
   4336  *aColor = aFrame->Style()->GetVisitedDependentColor(
   4337      nsStyleBorder::BorderColorFieldFor(physicalSide));
   4338 
   4339  if (aWidth) {
   4340    *aWidth = styleData->GetComputedBorderWidth(physicalSide);
   4341  }
   4342 }
   4343 
   4344 /** coerce the paint style as required by CSS2.1
   4345 * @param aFrame           - query the info for this frame
   4346 * @param aTableWM         - the writing mode of the frame
   4347 * @param aSide            - the side of the frame
   4348 * @param aStyle           - the border style
   4349 * @param aColor           - the border color
   4350 */
   4351 static void GetPaintStyleInfo(const nsIFrame* aFrame, WritingMode aTableWM,
   4352                              LogicalSide aSide, StyleBorderStyle* aStyle,
   4353                              nscolor* aColor) {
   4354  GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor);
   4355  if (StyleBorderStyle::Inset == *aStyle) {
   4356    *aStyle = StyleBorderStyle::Ridge;
   4357  } else if (StyleBorderStyle::Outset == *aStyle) {
   4358    *aStyle = StyleBorderStyle::Groove;
   4359  }
   4360 }
   4361 
   4362 class nsDelayedCalcBCBorders : public Runnable {
   4363 public:
   4364  explicit nsDelayedCalcBCBorders(nsIFrame* aFrame)
   4365      : mozilla::Runnable("nsDelayedCalcBCBorders"), mFrame(aFrame) {}
   4366 
   4367  NS_IMETHOD Run() override {
   4368    if (mFrame) {
   4369      nsTableFrame* tableFrame = static_cast<nsTableFrame*>(mFrame.GetFrame());
   4370      if (tableFrame->NeedToCalcBCBorders()) {
   4371        tableFrame->CalcBCBorders();
   4372      }
   4373    }
   4374    return NS_OK;
   4375  }
   4376 
   4377 private:
   4378  WeakFrame mFrame;
   4379 };
   4380 
   4381 bool nsTableFrame::BCRecalcNeeded(ComputedStyle* aOldComputedStyle,
   4382                                  ComputedStyle* aNewComputedStyle) {
   4383  // Attention: the old ComputedStyle is the one we're forgetting,
   4384  // and hence possibly completely bogus for GetStyle* purposes.
   4385  // We use PeekStyleData instead.
   4386 
   4387  const nsStyleBorder* oldStyleData = aOldComputedStyle->StyleBorder();
   4388  const nsStyleBorder* newStyleData = aNewComputedStyle->StyleBorder();
   4389  nsChangeHint change = newStyleData->CalcDifference(*oldStyleData);
   4390  if (!change) {
   4391    return false;
   4392  }
   4393  if (change & nsChangeHint_NeedReflow) {
   4394    return true;  // the caller only needs to mark the bc damage area
   4395  }
   4396  if (change & nsChangeHint_RepaintFrame) {
   4397    // we need to recompute the borders and the caller needs to mark
   4398    // the bc damage area
   4399    // XXX In principle this should only be necessary for border style changes
   4400    // However the bc painting code tries to maximize the drawn border segments
   4401    // so it stores in the cellmap where a new border segment starts and this
   4402    // introduces a unwanted cellmap data dependence on color
   4403    nsCOMPtr<nsIRunnable> evt = new nsDelayedCalcBCBorders(this);
   4404    nsresult rv = GetContent()->OwnerDoc()->Dispatch(evt.forget());
   4405    return NS_SUCCEEDED(rv);
   4406  }
   4407  return false;
   4408 }
   4409 
   4410 // Compare two border segments, this comparison depends whether the two
   4411 // segments meet at a corner and whether the second segment is inline-dir.
   4412 // The return value is whichever of aBorder1 or aBorder2 dominates.
   4413 static const BCCellBorder& CompareBorders(
   4414    bool aIsCorner,  // Pass true for corner calculations
   4415    const BCCellBorder& aBorder1, const BCCellBorder& aBorder2,
   4416    bool aSecondIsInlineDir, bool* aFirstDominates = nullptr) {
   4417  bool firstDominates = true;
   4418 
   4419  if (StyleBorderStyle::Hidden == aBorder1.style) {
   4420    firstDominates = !aIsCorner;
   4421  } else if (StyleBorderStyle::Hidden == aBorder2.style) {
   4422    firstDominates = aIsCorner;
   4423  } else if (aBorder1.width < aBorder2.width) {
   4424    firstDominates = false;
   4425  } else if (aBorder1.width == aBorder2.width) {
   4426    if (static_cast<uint8_t>(aBorder1.style) <
   4427        static_cast<uint8_t>(aBorder2.style)) {
   4428      firstDominates = false;
   4429    } else if (aBorder1.style == aBorder2.style) {
   4430      if (aBorder1.owner == aBorder2.owner) {
   4431        firstDominates = !aSecondIsInlineDir;
   4432      } else if (aBorder1.owner < aBorder2.owner) {
   4433        firstDominates = false;
   4434      }
   4435    }
   4436  }
   4437 
   4438  if (aFirstDominates) {
   4439    *aFirstDominates = firstDominates;
   4440  }
   4441 
   4442  if (firstDominates) {
   4443    return aBorder1;
   4444  }
   4445  return aBorder2;
   4446 }
   4447 
   4448 /** calc the dominant border by considering the table, row/col group, row/col,
   4449 * cell.
   4450 * Depending on whether the side is block-dir or inline-dir and whether
   4451 * adjacent frames are taken into account the ownership of a single border
   4452 * segment is defined. The return value is the dominating border
   4453 * The cellmap stores only bstart and istart borders for each cellmap position.
   4454 * If the cell border is owned by the cell that is istart-wards of the border
   4455 * it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other
   4456 * scenarios with a adjacent owner.
   4457 * @param xxxFrame         - the frame for style information, might be zero if
   4458 *                           it should not be considered
   4459 * @param aTableWM         - the writing mode of the frame
   4460 * @param aSide            - side of the frames that should be considered
   4461 * @param aAja             - the border comparison takes place from the point of
   4462 *                           a frame that is adjacent to the cellmap entry, for
   4463 *                           when a cell owns its lower border it will be the
   4464 *                           adjacent owner as in the cellmap only bstart and
   4465 *                           istart borders are stored.
   4466 */
   4467 static BCCellBorder CompareBorders(
   4468    const nsIFrame* aTableFrame, const nsIFrame* aColGroupFrame,
   4469    const nsIFrame* aColFrame, const nsIFrame* aRowGroupFrame,
   4470    const nsIFrame* aRowFrame, const nsIFrame* aCellFrame, WritingMode aTableWM,
   4471    LogicalSide aSide, bool aAja) {
   4472  BCCellBorder border, tempBorder;
   4473  bool inlineAxis = IsBlock(aSide);
   4474 
   4475  // start with the table as dominant if present
   4476  if (aTableFrame) {
   4477    GetColorAndStyle(aTableFrame, aTableWM, aSide, &border.style, &border.color,
   4478                     &border.width);
   4479    border.owner = eTableOwner;
   4480    if (StyleBorderStyle::Hidden == border.style) {
   4481      return border;
   4482    }
   4483  }
   4484  // see if the colgroup is dominant
   4485  if (aColGroupFrame) {
   4486    GetColorAndStyle(aColGroupFrame, aTableWM, aSide, &tempBorder.style,
   4487                     &tempBorder.color, &tempBorder.width);
   4488    tempBorder.owner = aAja && !inlineAxis ? eAjaColGroupOwner : eColGroupOwner;
   4489    // pass here and below false for aSecondIsInlineDir as it is only used for
   4490    // corner calculations.
   4491    border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
   4492    if (StyleBorderStyle::Hidden == border.style) {
   4493      return border;
   4494    }
   4495  }
   4496  // see if the col is dominant
   4497  if (aColFrame) {
   4498    GetColorAndStyle(aColFrame, aTableWM, aSide, &tempBorder.style,
   4499                     &tempBorder.color, &tempBorder.width);
   4500    tempBorder.owner = aAja && !inlineAxis ? eAjaColOwner : eColOwner;
   4501    border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
   4502    if (StyleBorderStyle::Hidden == border.style) {
   4503      return border;
   4504    }
   4505  }
   4506  // see if the rowgroup is dominant
   4507  if (aRowGroupFrame) {
   4508    GetColorAndStyle(aRowGroupFrame, aTableWM, aSide, &tempBorder.style,
   4509                     &tempBorder.color, &tempBorder.width);
   4510    tempBorder.owner = aAja && inlineAxis ? eAjaRowGroupOwner : eRowGroupOwner;
   4511    border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
   4512    if (StyleBorderStyle::Hidden == border.style) {
   4513      return border;
   4514    }
   4515  }
   4516  // see if the row is dominant
   4517  if (aRowFrame) {
   4518    GetColorAndStyle(aRowFrame, aTableWM, aSide, &tempBorder.style,
   4519                     &tempBorder.color, &tempBorder.width);
   4520    tempBorder.owner = aAja && inlineAxis ? eAjaRowOwner : eRowOwner;
   4521    border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
   4522    if (StyleBorderStyle::Hidden == border.style) {
   4523      return border;
   4524    }
   4525  }
   4526  // see if the cell is dominant
   4527  if (aCellFrame) {
   4528    GetColorAndStyle(aCellFrame, aTableWM, aSide, &tempBorder.style,
   4529                     &tempBorder.color, &tempBorder.width);
   4530    tempBorder.owner = aAja ? eAjaCellOwner : eCellOwner;
   4531    border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
   4532  }
   4533  return border;
   4534 }
   4535 
   4536 static bool Perpendicular(mozilla::LogicalSide aSide1,
   4537                          mozilla::LogicalSide aSide2) {
   4538  return IsInline(aSide1) != IsInline(aSide2);
   4539 }
   4540 
   4541 // Initial value indicating that BCCornerInfo's ownerStyle hasn't been set yet.
   4542 #define BORDER_STYLE_UNSET static_cast<StyleBorderStyle>(255)
   4543 
   4544 struct BCCornerInfo {
   4545  BCCornerInfo() {
   4546    ownerColor = 0;
   4547    ownerWidth = subWidth = ownerElem = subSide = subElem = hasDashDot =
   4548        numSegs = bevel = 0;
   4549    ownerSide = static_cast<uint16_t>(LogicalSide::BStart);
   4550    ownerStyle = BORDER_STYLE_UNSET;
   4551    subStyle = StyleBorderStyle::Solid;
   4552  }
   4553 
   4554  void Set(mozilla::LogicalSide aSide, BCCellBorder border);
   4555 
   4556  void Update(mozilla::LogicalSide aSide, BCCellBorder border);
   4557 
   4558  nscolor ownerColor;           // color of borderOwner
   4559  uint16_t ownerWidth;          // width of borderOwner
   4560  uint16_t subWidth;            // width of the largest border intersecting the
   4561                                // border perpendicular to ownerSide
   4562  StyleBorderStyle subStyle;    // border style of subElem
   4563  StyleBorderStyle ownerStyle;  // border style of ownerElem
   4564  uint16_t ownerSide : 2;  // LogicalSide (e.g LogicalSide::BStart, etc) of the
   4565                           // border owning the corner relative to the corner
   4566  uint16_t
   4567      ownerElem : 4;  // elem type (e.g. eTable, eGroup, etc) owning the corner
   4568  uint16_t subSide : 2;  // side of border with subWidth relative to the corner
   4569  uint16_t subElem : 4;  // elem type (e.g. eTable, eGroup, etc) of sub owner
   4570  uint16_t hasDashDot : 1;  // does a dashed, dotted segment enter the corner,
   4571                            // they cannot be beveled
   4572  uint16_t numSegs : 3;     // number of segments entering corner
   4573  uint16_t bevel : 1;       // is the corner beveled (uses the above two fields
   4574                            // together with subWidth)
   4575  // 7 bits are unused
   4576 };
   4577 
   4578 // Start a new border at this corner, going in the direction of a given side.
   4579 void BCCornerInfo::Set(mozilla::LogicalSide aSide, BCCellBorder aBorder) {
   4580  // FIXME bug 1508921: We mask 4-bit BCBorderOwner enum to 3 bits to preserve
   4581  // buggy behavior found by the frame_above_rules_all.html mochitest.
   4582  ownerElem = aBorder.owner & 0x7;
   4583 
   4584  ownerStyle = aBorder.style;
   4585  ownerWidth = aBorder.width;
   4586  ownerColor = aBorder.color;
   4587  ownerSide = static_cast<uint16_t>(aSide);
   4588  hasDashDot = 0;
   4589  numSegs = 0;
   4590  if (aBorder.width > 0) {
   4591    numSegs++;
   4592    hasDashDot = (StyleBorderStyle::Dashed == aBorder.style) ||
   4593                 (StyleBorderStyle::Dotted == aBorder.style);
   4594  }
   4595  bevel = 0;
   4596  subWidth = 0;
   4597  // the following will get set later
   4598  subSide = static_cast<uint16_t>(IsInline(aSide) ? LogicalSide::BStart
   4599                                                  : LogicalSide::IStart);
   4600  subElem = eTableOwner;
   4601  subStyle = StyleBorderStyle::Solid;
   4602 }
   4603 
   4604 // Add a new border going in the direction of a given side, and update the
   4605 // dominant border.
   4606 void BCCornerInfo::Update(mozilla::LogicalSide aSide, BCCellBorder aBorder) {
   4607  if (ownerStyle == BORDER_STYLE_UNSET) {
   4608    Set(aSide, aBorder);
   4609  } else {
   4610    bool isInline = IsInline(aSide);  // relative to the corner
   4611    BCCellBorder oldBorder, tempBorder;
   4612    oldBorder.owner = (BCBorderOwner)ownerElem;
   4613    oldBorder.style = ownerStyle;
   4614    oldBorder.width = ownerWidth;
   4615    oldBorder.color = ownerColor;
   4616 
   4617    LogicalSide oldSide = LogicalSide(ownerSide);
   4618 
   4619    bool existingWins = false;
   4620    tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline,
   4621                                &existingWins);
   4622 
   4623    ownerElem = tempBorder.owner;
   4624    ownerStyle = tempBorder.style;
   4625    ownerWidth = tempBorder.width;
   4626    ownerColor = tempBorder.color;
   4627    if (existingWins) {  // existing corner is dominant
   4628      if (::Perpendicular(LogicalSide(ownerSide), aSide)) {
   4629        // see if the new sub info replaces the old
   4630        BCCellBorder subBorder;
   4631        subBorder.owner = (BCBorderOwner)subElem;
   4632        subBorder.style = subStyle;
   4633        subBorder.width = subWidth;
   4634        subBorder.color = 0;  // we are not interested in subBorder color
   4635        bool firstWins;
   4636 
   4637        tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline,
   4638                                    &firstWins);
   4639 
   4640        subElem = tempBorder.owner;
   4641        subStyle = tempBorder.style;
   4642        subWidth = tempBorder.width;
   4643        if (!firstWins) {
   4644          subSide = static_cast<uint16_t>(aSide);
   4645        }
   4646      }
   4647    } else {  // input args are dominant
   4648      ownerSide = static_cast<uint16_t>(aSide);
   4649      if (::Perpendicular(oldSide, LogicalSide(ownerSide))) {
   4650        subElem = oldBorder.owner;
   4651        subStyle = oldBorder.style;
   4652        subWidth = oldBorder.width;
   4653        subSide = static_cast<uint16_t>(oldSide);
   4654      }
   4655    }
   4656    if (aBorder.width > 0) {
   4657      numSegs++;
   4658      if (!hasDashDot && ((StyleBorderStyle::Dashed == aBorder.style) ||
   4659                          (StyleBorderStyle::Dotted == aBorder.style))) {
   4660        hasDashDot = 1;
   4661      }
   4662    }
   4663 
   4664    // bevel the corner if only two perpendicular non dashed/dotted segments
   4665    // enter the corner
   4666    bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot);
   4667  }
   4668 }
   4669 
   4670 struct BCCorners {
   4671  BCCorners(int32_t aNumCorners, int32_t aStartIndex);
   4672 
   4673  BCCornerInfo& operator[](int32_t i) const {
   4674    NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
   4675    return corners[std::clamp(i, startIndex, endIndex) - startIndex];
   4676  }
   4677 
   4678  int32_t startIndex;
   4679  int32_t endIndex;
   4680  UniquePtr<BCCornerInfo[]> corners;
   4681 };
   4682 
   4683 BCCorners::BCCorners(int32_t aNumCorners, int32_t aStartIndex) {
   4684  NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error");
   4685  startIndex = aStartIndex;
   4686  endIndex = aStartIndex + aNumCorners - 1;
   4687  corners = MakeUnique<BCCornerInfo[]>(aNumCorners);
   4688 }
   4689 
   4690 struct BCCellBorders {
   4691  BCCellBorders(int32_t aNumBorders, int32_t aStartIndex);
   4692 
   4693  BCCellBorder& operator[](int32_t i) const {
   4694    NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
   4695    return borders[std::clamp(i, startIndex, endIndex) - startIndex];
   4696  }
   4697 
   4698  int32_t startIndex;
   4699  int32_t endIndex;
   4700  UniquePtr<BCCellBorder[]> borders;
   4701 };
   4702 
   4703 BCCellBorders::BCCellBorders(int32_t aNumBorders, int32_t aStartIndex) {
   4704  NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error");
   4705  startIndex = aStartIndex;
   4706  endIndex = aStartIndex + aNumBorders - 1;
   4707  borders = MakeUnique<BCCellBorder[]>(aNumBorders);
   4708 }
   4709 
   4710 // this function sets the new border properties and returns true if the border
   4711 // segment will start a new segment and not be accumulated into the previous
   4712 // segment.
   4713 static bool SetBorder(const BCCellBorder& aNewBorder, BCCellBorder& aBorder) {
   4714  bool changed = (aNewBorder.style != aBorder.style) ||
   4715                 (aNewBorder.width != aBorder.width) ||
   4716                 (aNewBorder.color != aBorder.color);
   4717  aBorder.color = aNewBorder.color;
   4718  aBorder.width = aNewBorder.width;
   4719  aBorder.style = aNewBorder.style;
   4720  aBorder.owner = aNewBorder.owner;
   4721 
   4722  return changed;
   4723 }
   4724 
   4725 // this function will set the inline-dir border. It will return true if the
   4726 // existing segment will not be continued. Having a block-dir owner of a corner
   4727 // should also start a new segment.
   4728 static bool SetInlineDirBorder(const BCCellBorder& aNewBorder,
   4729                               const BCCornerInfo& aCorner,
   4730                               BCCellBorder& aBorder) {
   4731  bool startSeg = ::SetBorder(aNewBorder, aBorder);
   4732  if (!startSeg) {
   4733    startSeg = !IsInline(LogicalSide(aCorner.ownerSide));
   4734  }
   4735  return startSeg;
   4736 }
   4737 
   4738 // Make the damage area larger on the top and bottom by at least one row and on
   4739 // the left and right at least one column. This is done so that adjacent
   4740 // elements are part of the border calculations. The extra segments and borders
   4741 // outside the actual damage area will not be updated in the cell map, because
   4742 // they in turn would need info from adjacent segments outside the damage area
   4743 // to be accurate.
   4744 void nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const {
   4745  int32_t numRows = GetRowCount();
   4746  int32_t numCols = GetColCount();
   4747 
   4748  int32_t firstColIdx = aArea.StartCol();
   4749  int32_t lastColIdx = aArea.EndCol() - 1;
   4750  int32_t startRowIdx = aArea.StartRow();
   4751  int32_t endRowIdx = aArea.EndRow() - 1;
   4752 
   4753  // expand the damage area in each direction
   4754  if (firstColIdx > 0) {
   4755    firstColIdx--;
   4756  }
   4757  if (lastColIdx < (numCols - 1)) {
   4758    lastColIdx++;
   4759  }
   4760  if (startRowIdx > 0) {
   4761    startRowIdx--;
   4762  }
   4763  if (endRowIdx < (numRows - 1)) {
   4764    endRowIdx++;
   4765  }
   4766  // Check the damage area so that there are no cells spanning in or out. If
   4767  // there are any then make the damage area as big as the table, similarly to
   4768  // the way the cell map decides whether to rebuild versus expand. This could
   4769  // be optimized to expand to the smallest area that contains no spanners, but
   4770  // it may not be worth the effort in general, and it would need to be done in
   4771  // the cell map as well.
   4772  bool haveSpanner = false;
   4773  if ((firstColIdx > 0) || (lastColIdx < (numCols - 1)) || (startRowIdx > 0) ||
   4774      (endRowIdx < (numRows - 1))) {
   4775    nsTableCellMap* tableCellMap = GetCellMap();
   4776    if (!tableCellMap) ABORT0();
   4777    // Get the ordered row groups
   4778    RowGroupArray rowGroups = OrderedRowGroups();
   4779 
   4780    // Scope outside loop to be used as hint.
   4781    nsCellMap* cellMap = nullptr;
   4782    for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
   4783      nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
   4784      int32_t rgStartY = rgFrame->GetStartRowIndex();
   4785      int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1;
   4786      if (endRowIdx < rgStartY) {
   4787        break;
   4788      }
   4789      cellMap = tableCellMap->GetMapFor(rgFrame, cellMap);
   4790      if (!cellMap) ABORT0();
   4791      // check for spanners from above and below
   4792      if ((startRowIdx > 0) && (startRowIdx >= rgStartY) &&
   4793          (startRowIdx <= rgEndY)) {
   4794        if (uint32_t(startRowIdx - rgStartY) >= cellMap->mRows.Length())
   4795          ABORT0();
   4796        const nsCellMap::CellDataArray& row =
   4797            cellMap->mRows[startRowIdx - rgStartY];
   4798        for (int32_t x = firstColIdx; x <= lastColIdx; x++) {
   4799          CellData* cellData = row.SafeElementAt(x);
   4800          if (cellData && (cellData->IsRowSpan())) {
   4801            haveSpanner = true;
   4802            break;
   4803          }
   4804        }
   4805        if (endRowIdx < rgEndY) {
   4806          if (uint32_t(endRowIdx + 1 - rgStartY) >= cellMap->mRows.Length())
   4807            ABORT0();
   4808          const nsCellMap::CellDataArray& row2 =
   4809              cellMap->mRows[endRowIdx + 1 - rgStartY];
   4810          for (int32_t x = firstColIdx; x <= lastColIdx; x++) {
   4811            CellData* cellData = row2.SafeElementAt(x);
   4812            if (cellData && (cellData->IsRowSpan())) {
   4813              haveSpanner = true;
   4814              break;
   4815            }
   4816          }
   4817        }
   4818      }
   4819      // check for spanners on the left and right
   4820      int32_t iterStartY;
   4821      int32_t iterEndY;
   4822      if ((startRowIdx >= rgStartY) && (startRowIdx <= rgEndY)) {
   4823        // the damage area starts in the row group
   4824        iterStartY = startRowIdx;
   4825        iterEndY = std::min(endRowIdx, rgEndY);
   4826      } else if ((endRowIdx >= rgStartY) && (endRowIdx <= rgEndY)) {
   4827        // the damage area ends in the row group
   4828        iterStartY = rgStartY;
   4829        iterEndY = endRowIdx;
   4830      } else if ((rgStartY >= startRowIdx) && (rgEndY <= endRowIdx)) {
   4831        // the damage area contains the row group
   4832        iterStartY = rgStartY;
   4833        iterEndY = rgEndY;
   4834      } else {
   4835        // the damage area does not overlap the row group
   4836        continue;
   4837      }
   4838      NS_ASSERTION(iterStartY >= 0 && iterEndY >= 0,
   4839                   "table index values are expected to be nonnegative");
   4840      for (int32_t y = iterStartY; y <= iterEndY; y++) {
   4841        if (uint32_t(y - rgStartY) >= cellMap->mRows.Length()) ABORT0();
   4842        const nsCellMap::CellDataArray& row = cellMap->mRows[y - rgStartY];
   4843        CellData* cellData = row.SafeElementAt(firstColIdx);
   4844        if (cellData && (cellData->IsColSpan())) {
   4845          haveSpanner = true;
   4846          break;
   4847        }
   4848        if (lastColIdx < (numCols - 1)) {
   4849          cellData = row.SafeElementAt(lastColIdx + 1);
   4850          if (cellData && (cellData->IsColSpan())) {
   4851            haveSpanner = true;
   4852            break;
   4853          }
   4854        }
   4855      }
   4856    }
   4857  }
   4858 
   4859  // If the damage area includes the edge of the table, we have to expand
   4860  // the damage area across that whole edge. This is because table-edge
   4861  // borders take the maximum border width among all cells on that edge.
   4862  // i.e. If the first row is damaged, then we consider all the cols to
   4863  // be damaged, and vice versa.
   4864  if (haveSpanner || startRowIdx == 0 || endRowIdx == numRows - 1) {
   4865    aArea.StartCol() = 0;
   4866    aArea.ColCount() = numCols;
   4867  } else {
   4868    aArea.StartCol() = firstColIdx;
   4869    aArea.ColCount() = 1 + lastColIdx - firstColIdx;
   4870  }
   4871 
   4872  if (haveSpanner || firstColIdx == 0 || lastColIdx == numCols - 1) {
   4873    aArea.StartRow() = 0;
   4874    aArea.RowCount() = numRows;
   4875  } else {
   4876    aArea.StartRow() = startRowIdx;
   4877    aArea.RowCount() = 1 + endRowIdx - startRowIdx;
   4878  }
   4879 }
   4880 
   4881 #define ADJACENT true
   4882 #define INLINE_DIR true
   4883 
   4884 void BCMapTableInfo::SetTableIStartBorderWidth(nscoord aWidth) {
   4885  mTableBCData->mIStartBorderWidth =
   4886      std::max(mTableBCData->mIStartBorderWidth, aWidth);
   4887 }
   4888 
   4889 void BCMapTableInfo::SetTableIEndBorderWidth(nscoord aWidth) {
   4890  mTableBCData->mIEndBorderWidth =
   4891      std::max(mTableBCData->mIEndBorderWidth, aWidth);
   4892 }
   4893 
   4894 void BCMapTableInfo::SetTableBStartBorderWidth(nscoord aWidth) {
   4895  mTableBCData->mBStartBorderWidth =
   4896      std::max(mTableBCData->mBStartBorderWidth, aWidth);
   4897 }
   4898 
   4899 void BCMapTableInfo::SetTableBEndBorderWidth(nscoord aWidth) {
   4900  mTableBCData->mBEndBorderWidth =
   4901      std::max(mTableBCData->mBEndBorderWidth, aWidth);
   4902 }
   4903 
   4904 void BCMapCellInfo::ResetIStartBorderWidths() {
   4905  if (mCell) {
   4906    mCell->SetBorderWidth(LogicalSide::IStart, 0);
   4907  }
   4908  if (mStartCol) {
   4909    mStartCol->SetIStartBorderWidth(0);
   4910  }
   4911 }
   4912 
   4913 void BCMapCellInfo::ResetIEndBorderWidths() {
   4914  if (mCell) {
   4915    mCell->SetBorderWidth(LogicalSide::IEnd, 0);
   4916  }
   4917  if (mEndCol) {
   4918    mEndCol->SetIEndBorderWidth(0);
   4919  }
   4920 }
   4921 
   4922 void BCMapCellInfo::ResetBStartBorderWidths() {
   4923  if (mCell) {
   4924    mCell->SetBorderWidth(LogicalSide::BStart, 0);
   4925  }
   4926  if (mStartRow) {
   4927    mStartRow->SetBStartBCBorderWidth(0);
   4928  }
   4929 }
   4930 
   4931 void BCMapCellInfo::ResetBEndBorderWidths() {
   4932  if (mCell) {
   4933    mCell->SetBorderWidth(LogicalSide::BEnd, 0);
   4934  }
   4935  if (mEndRow) {
   4936    mEndRow->SetBEndBCBorderWidth(0);
   4937  }
   4938 }
   4939 
   4940 void BCMapCellInfo::SetIStartBorderWidths(nscoord aWidth) {
   4941  if (mCell) {
   4942    mCell->SetBorderWidth(
   4943        LogicalSide::IStart,
   4944        std::max(aWidth, mCell->GetBorderWidth(LogicalSide::IStart)));
   4945  }
   4946  if (mStartCol) {
   4947    nscoord half = BC_BORDER_END_HALF(aWidth);
   4948    mStartCol->SetIStartBorderWidth(
   4949        std::max(half, mStartCol->GetIStartBorderWidth()));
   4950  }
   4951 }
   4952 
   4953 void BCMapCellInfo::SetIEndBorderWidths(nscoord aWidth) {
   4954  // update the borders of the cells and cols affected
   4955  if (mCell) {
   4956    mCell->SetBorderWidth(
   4957        LogicalSide::IEnd,
   4958        std::max(aWidth, mCell->GetBorderWidth(LogicalSide::IEnd)));
   4959  }
   4960  if (mEndCol) {
   4961    nscoord half = BC_BORDER_START_HALF(aWidth);
   4962    mEndCol->SetIEndBorderWidth(std::max(half, mEndCol->GetIEndBorderWidth()));
   4963  }
   4964 }
   4965 
   4966 void BCMapCellInfo::SetBStartBorderWidths(nscoord aWidth) {
   4967  if (mCell) {
   4968    mCell->SetBorderWidth(
   4969        LogicalSide::BStart,
   4970        std::max(aWidth, mCell->GetBorderWidth(LogicalSide::BStart)));
   4971  }
   4972  if (mStartRow) {
   4973    nscoord half = BC_BORDER_END_HALF(aWidth);
   4974    mStartRow->SetBStartBCBorderWidth(
   4975        std::max(half, mStartRow->GetBStartBCBorderWidth()));
   4976  }
   4977 }
   4978 
   4979 void BCMapCellInfo::SetBEndBorderWidths(nscoord aWidth) {
   4980  // update the borders of the affected cells and rows
   4981  if (mCell) {
   4982    mCell->SetBorderWidth(
   4983        LogicalSide::BEnd,
   4984        std::max(aWidth, mCell->GetBorderWidth(LogicalSide::BEnd)));
   4985  }
   4986  if (mEndRow) {
   4987    nscoord half = BC_BORDER_START_HALF(aWidth);
   4988    mEndRow->SetBEndBCBorderWidth(
   4989        std::max(half, mEndRow->GetBEndBCBorderWidth()));
   4990  }
   4991 }
   4992 
   4993 void BCMapCellInfo::SetColumn(int32_t aColX) {
   4994  mCurrentColFrame = mTableFirstInFlow->GetColFrame(aColX);
   4995  mCurrentColGroupFrame =
   4996      static_cast<nsTableColGroupFrame*>(mCurrentColFrame->GetParent());
   4997  if (!mCurrentColGroupFrame) {
   4998    NS_ERROR("null mCurrentColGroupFrame");
   4999  }
   5000 }
   5001 
   5002 void BCMapCellInfo::IncrementRow(bool aResetToBStartRowOfCell) {
   5003  mCurrentRowFrame =
   5004      aResetToBStartRowOfCell ? mStartRow : mCurrentRowFrame->GetNextRow();
   5005 }
   5006 
   5007 BCCellBorder BCMapCellInfo::GetBStartEdgeBorder() {
   5008  return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame,
   5009                        mRowGroup, mStartRow, mCell, mTableWM,
   5010                        LogicalSide::BStart, !ADJACENT);
   5011 }
   5012 
   5013 BCCellBorder BCMapCellInfo::GetBEndEdgeBorder() {
   5014  return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame,
   5015                        mRowGroup, mEndRow, mCell, mTableWM, LogicalSide::BEnd,
   5016                        ADJACENT);
   5017 }
   5018 BCCellBorder BCMapCellInfo::GetIStartEdgeBorder() {
   5019  return CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup,
   5020                        mCurrentRowFrame, mCell, mTableWM, LogicalSide::IStart,
   5021                        !ADJACENT);
   5022 }
   5023 BCCellBorder BCMapCellInfo::GetIEndEdgeBorder() {
   5024  return CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
   5025                        mCurrentRowFrame, mCell, mTableWM, LogicalSide::IEnd,
   5026                        ADJACENT);
   5027 }
   5028 BCCellBorder BCMapCellInfo::GetIEndInternalBorder() {
   5029  const nsIFrame* cg = mCgAtEnd ? mColGroup : nullptr;
   5030  return CompareBorders(nullptr, cg, mEndCol, nullptr, nullptr, mCell, mTableWM,
   5031                        LogicalSide::IEnd, ADJACENT);
   5032 }
   5033 
   5034 BCCellBorder BCMapCellInfo::GetIStartInternalBorder() {
   5035  const nsIFrame* cg = mCgAtStart ? mColGroup : nullptr;
   5036  return CompareBorders(nullptr, cg, mStartCol, nullptr, nullptr, mCell,
   5037                        mTableWM, LogicalSide::IStart, !ADJACENT);
   5038 }
   5039 
   5040 BCCellBorder BCMapCellInfo::GetBEndInternalBorder() {
   5041  const nsIFrame* rg = mRgAtEnd ? mRowGroup : nullptr;
   5042  return CompareBorders(nullptr, nullptr, nullptr, rg, mEndRow, mCell, mTableWM,
   5043                        LogicalSide::BEnd, ADJACENT);
   5044 }
   5045 
   5046 BCCellBorder BCMapCellInfo::GetBStartInternalBorder() {
   5047  const nsIFrame* rg = mRgAtStart ? mRowGroup : nullptr;
   5048  return CompareBorders(nullptr, nullptr, nullptr, rg, mStartRow, mCell,
   5049                        mTableWM, LogicalSide::BStart, !ADJACENT);
   5050 }
   5051 
   5052 //  Calculate border information for border-collapsed tables.
   5053 //  Because borders of table/row/cell, etc merge into one, we need to
   5054 //  determine which border dominates at each cell. In addition, corner-specific
   5055 //  information, e.g. bevelling, is computed as well.
   5056 //
   5057 //  Here is the order for storing border edges in the cell map as a cell is
   5058 //  processed.
   5059 //
   5060 //  For each cell, at least 4 edges are processed:
   5061 //  * There are colspan * N block-start and block-end edges.
   5062 //  * There are rowspan * N inline-start and inline-end edges.
   5063 //
   5064 //  1) If the cell being processed is at the block-start of the table, store the
   5065 //     block-start edge.
   5066 //  2) If the cell being processed is at the inline-start of the table, store
   5067 //  the
   5068 //     inline-start edge.
   5069 //  3) Store the inline-end edge.
   5070 //  4) Store the block-end edge.
   5071 //
   5072 //  These steps are traced by calls to `SetBCBorderEdge`.
   5073 //
   5074 //  Corners are indexed by columns only, to avoid allocating a full row * col
   5075 //  array of `BCCornerInfo`. This trades off memory allocation versus moving
   5076 //  previous corner information around.
   5077 //
   5078 //  For each cell:
   5079 //  1) If the cell is at the block-start of the table, but not at the
   5080 //  inline-start of the table, store its block-start inline-start corner.
   5081 //
   5082 //  2) If the cell is at the inline-start of the table, store the block-start
   5083 //  inline-start corner.
   5084 //
   5085 //  3) If the cell is at the block-start inline-end of the table, or not at the
   5086 //  block-start of the table, store the block-start inline-end corner.
   5087 //
   5088 //  4) If the cell is at the block-end inline-end of the table, store the
   5089 //  block-end inline-end corner.
   5090 //
   5091 //  5) If the cell is at the block-end of the table, store the block-end
   5092 //  inline-start.
   5093 //
   5094 //  Visually, it looks like this:
   5095 //
   5096 //  2--1--1--1--1--1--3
   5097 //  |  |  |  |  |  |  |
   5098 //  2--3--3--3--3--3--3
   5099 //  |  |  |  |  |  |  |
   5100 //  2--3--3--3--3--3--3
   5101 //  |  |  |  |  |  |  |
   5102 //  5--5--5--5--5--5--4
   5103 //
   5104 //  For rowspan/colspan cells, the latest border information is propagated
   5105 //  along its "corners".
   5106 //
   5107 //  These steps are traced by calls to `SetBCBorderCorner`.
   5108 void nsTableFrame::CalcBCBorders() {
   5109  NS_ASSERTION(IsBorderCollapse(),
   5110               "calling CalcBCBorders on separated-border table");
   5111  nsTableCellMap* tableCellMap = GetCellMap();
   5112  if (!tableCellMap) ABORT0();
   5113  int32_t numRows = GetRowCount();
   5114  int32_t numCols = GetColCount();
   5115  if (!numRows || !numCols) {
   5116    return;  // nothing to do
   5117  }
   5118 
   5119  // Get the property holding the table damage area and border widths
   5120  TableBCData* propData = GetTableBCData();
   5121  if (!propData) ABORT0();
   5122 
   5123  TableArea damageArea(propData->mDamageArea);
   5124  // See documentation for why we do this.
   5125  ExpandBCDamageArea(damageArea);
   5126 
   5127  // We accumulate border widths as we process the cells, so we need
   5128  // to reset it once in the beginning.
   5129  bool tableBorderReset[4];
   5130  for (uint32_t sideX = 0; sideX < std::size(tableBorderReset); sideX++) {
   5131    tableBorderReset[sideX] = false;
   5132  }
   5133 
   5134  // Storage for block-direction borders from the previous row, indexed by
   5135  // columns.
   5136  BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1,
   5137                                    damageArea.StartCol());
   5138  if (!lastBlockDirBorders.borders) ABORT0();
   5139  if (damageArea.StartRow() != 0) {
   5140    // Ok, we've filled with information about the previous row's borders with
   5141    // the default state, which is "no borders." This is incorrect, and leaving
   5142    // it will result in an erroneous behaviour if the previous row did have
   5143    // borders, and the dirty rows don't, as we will not mark the beginning of
   5144    // the no border segment.
   5145    TableArea prevRowArea(damageArea.StartCol(), damageArea.StartRow() - 1,
   5146                          damageArea.ColCount(), 1);
   5147    BCMapCellIterator iter(this, prevRowArea);
   5148    BCMapCellInfo info(this);
   5149    for (iter.First(info); !iter.mAtEnd; iter.Next(info)) {
   5150      if (info.mColIndex == prevRowArea.StartCol()) {
   5151        lastBlockDirBorders.borders[0] = info.GetIStartEdgeBorder();
   5152      }
   5153      lastBlockDirBorders.borders[info.mColIndex - prevRowArea.StartCol() + 1] =
   5154          info.GetIEndEdgeBorder();
   5155    }
   5156  }
   5157  // Inline direction border at block start of the table, computed by the
   5158  // previous cell. Unused afterwards.
   5159  Maybe<BCCellBorder> firstRowBStartEdgeBorder;
   5160  BCCellBorder lastBEndBorder;
   5161  // Storage for inline-direction borders from previous cells, indexed by
   5162  // columns.
   5163  // TODO(dshin): Why ColCount + 1? Number of inline segments should match
   5164  // column count exactly, unlike block direction segments...
   5165  BCCellBorders lastBEndBorders(damageArea.ColCount() + 1,
   5166                                damageArea.StartCol());
   5167  if (!lastBEndBorders.borders) ABORT0();
   5168 
   5169  BCMapCellInfo info(this);
   5170  // TODO(dshin): This is basically propData, except it uses first-in-flow's
   5171  // data. Consult the definition of `TableBCDataProperty` regarding
   5172  // using the first-in-flow only.
   5173  BCMapTableInfo tableInfo(this);
   5174 
   5175  // Block-start corners of the cell being traversed, indexed by columns.
   5176  BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol());
   5177  if (!bStartCorners.corners) ABORT0();
   5178  // Block-end corners of the cell being traversed, indexed by columns.
   5179  // Note that when a new row starts, they become block-start corners and used
   5180  // as such, until cleared with `Set`.
   5181  BCCorners bEndCorners(damageArea.ColCount() + 1, damageArea.StartCol());
   5182  if (!bEndCorners.corners) ABORT0();
   5183 
   5184  BCMapCellIterator iter(this, damageArea);
   5185  for (iter.First(info); !iter.mAtEnd; iter.Next(info)) {
   5186    // see if firstRowBStartEdgeBorder, lastBEndBorder need to be reset
   5187    if (iter.IsNewRow()) {
   5188      if (info.mRowIndex == 0) {
   5189        BCCellBorder border;
   5190        if (info.mColIndex == 0) {
   5191          border.Reset(info.mRowIndex, info.mRowSpan);
   5192        } else {
   5193          // Similar to lastBlockDirBorders, the previous block-start border
   5194          // is filled by actually quering the adjacent cell.
   5195          BCMapCellInfo ajaInfo(this);
   5196          iter.PeekIStart(info, info.mRowIndex, ajaInfo);
   5197          border = ajaInfo.GetBStartEdgeBorder();
   5198        }
   5199        firstRowBStartEdgeBorder = Some(border);
   5200      } else {
   5201        firstRowBStartEdgeBorder = Nothing{};
   5202      }
   5203      if (info.mColIndex == 0) {
   5204        lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
   5205      } else {
   5206        // Same as above, but for block-end border.
   5207        BCMapCellInfo ajaInfo(this);
   5208        iter.PeekIStart(info, info.mRowIndex, ajaInfo);
   5209        lastBEndBorder = ajaInfo.GetBEndEdgeBorder();
   5210      }
   5211    } else if (info.mColIndex > damageArea.StartCol()) {
   5212      lastBEndBorder = lastBEndBorders[info.mColIndex - 1];
   5213      if (lastBEndBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) {
   5214        // the bEnd border's iStart edge butts against the middle of a rowspan
   5215        lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
   5216      }
   5217    }
   5218 
   5219    // find the dominant border considering the cell's bStart border and the
   5220    // table, row group, row if the border is at the bStart of the table,
   5221    // otherwise it was processed in a previous row
   5222    if (0 == info.mRowIndex) {
   5223      uint8_t idxBStart = static_cast<uint8_t>(LogicalSide::BStart);
   5224      if (!tableBorderReset[idxBStart]) {
   5225        tableInfo.ResetTableBStartBorderWidth();
   5226        tableBorderReset[idxBStart] = true;
   5227      }
   5228      bool reset = false;
   5229      for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
   5230           colIdx++) {
   5231        info.SetColumn(colIdx);
   5232        BCCellBorder currentBorder = info.GetBStartEdgeBorder();
   5233        BCCornerInfo& bStartIStartCorner = bStartCorners[colIdx];
   5234        // Mark inline-end direction border from this corner.
   5235        if (0 == colIdx) {
   5236          bStartIStartCorner.Set(LogicalSide::IEnd, currentBorder);
   5237        } else {
   5238          bStartIStartCorner.Update(LogicalSide::IEnd, currentBorder);
   5239          tableCellMap->SetBCBorderCorner(
   5240              LogicalCorner::BStartIStart, *iter.mCellMap, 0, 0, colIdx,
   5241              LogicalSide(bStartIStartCorner.ownerSide),
   5242              bStartIStartCorner.subWidth, bStartIStartCorner.bevel);
   5243        }
   5244        // Above, we set the corner `colIndex` column as having a border towards
   5245        // inline-end, heading towards the next column. Vice versa is also true,
   5246        // where the next column has a border heading towards this column.
   5247        bStartCorners[colIdx + 1].Set(LogicalSide::IStart, currentBorder);
   5248        MOZ_ASSERT(firstRowBStartEdgeBorder,
   5249                   "Inline start border tracking not set?");
   5250        // update firstRowBStartEdgeBorder and see if a new segment starts
   5251        bool startSeg =
   5252            firstRowBStartEdgeBorder
   5253                ? SetInlineDirBorder(currentBorder, bStartIStartCorner,
   5254                                     firstRowBStartEdgeBorder.ref())
   5255                : true;
   5256        // store the border segment in the cell map
   5257        tableCellMap->SetBCBorderEdge(LogicalSide::BStart, *iter.mCellMap, 0, 0,
   5258                                      colIdx, 1, currentBorder.owner,
   5259                                      currentBorder.width, startSeg);
   5260 
   5261        // Set border width at block-start (table-wide and for the cell), but
   5262        // only if it's the largest we've encountered.
   5263        tableInfo.SetTableBStartBorderWidth(currentBorder.width);
   5264        if (!reset) {
   5265          info.ResetBStartBorderWidths();
   5266          reset = true;
   5267        }
   5268        info.SetBStartBorderWidths(currentBorder.width);
   5269      }
   5270    } else {
   5271      // see if the bStart border needs to be the start of a segment due to a
   5272      // block-dir border owning the corner
   5273      if (info.mColIndex > 0) {
   5274        BCData& data = info.mCellData->mData;
   5275        if (!data.IsBStartStart()) {
   5276          LogicalSide cornerSide;
   5277          bool bevel;
   5278          data.GetCorner(cornerSide, bevel);
   5279          if (IsBlock(cornerSide)) {
   5280            data.SetBStartStart(true);
   5281          }
   5282        }
   5283      }
   5284    }
   5285 
   5286    // find the dominant border considering the cell's iStart border and the
   5287    // table, col group, col if the border is at the iStart of the table,
   5288    // otherwise it was processed in a previous col
   5289    if (0 == info.mColIndex) {
   5290      uint8_t idxIStart = static_cast<uint8_t>(LogicalSide::IStart);
   5291      if (!tableBorderReset[idxIStart]) {
   5292        tableInfo.ResetTableIStartBorderWidth();
   5293        tableBorderReset[idxIStart] = true;
   5294      }
   5295      info.mCurrentRowFrame = nullptr;
   5296      bool reset = false;
   5297      for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
   5298           rowB++) {
   5299        info.IncrementRow(rowB == info.mRowIndex);
   5300        BCCellBorder currentBorder = info.GetIStartEdgeBorder();
   5301        BCCornerInfo& bStartIStartCorner =
   5302            (0 == rowB) ? bStartCorners[0] : bEndCorners[0];
   5303        bStartIStartCorner.Update(LogicalSide::BEnd, currentBorder);
   5304        tableCellMap->SetBCBorderCorner(
   5305            LogicalCorner::BStartIStart, *iter.mCellMap, iter.mRowGroupStart,
   5306            rowB, 0, LogicalSide(bStartIStartCorner.ownerSide),
   5307            bStartIStartCorner.subWidth, bStartIStartCorner.bevel);
   5308        bEndCorners[0].Set(LogicalSide::BStart, currentBorder);
   5309 
   5310        // update lastBlockDirBorders and see if a new segment starts
   5311        bool startSeg = SetBorder(currentBorder, lastBlockDirBorders[0]);
   5312        // store the border segment in the cell map
   5313        tableCellMap->SetBCBorderEdge(LogicalSide::IStart, *iter.mCellMap,
   5314                                      iter.mRowGroupStart, rowB, info.mColIndex,
   5315                                      1, currentBorder.owner,
   5316                                      currentBorder.width, startSeg);
   5317        // Set border width at inline-start (table-wide and for the cell), but
   5318        // only if it's the largest we've encountered.
   5319        tableInfo.SetTableIStartBorderWidth(currentBorder.width);
   5320        if (!reset) {
   5321          info.ResetIStartBorderWidths();
   5322          reset = true;
   5323        }
   5324        info.SetIStartBorderWidths(currentBorder.width);
   5325      }
   5326    }
   5327 
   5328    // find the dominant border considering the cell's iEnd border, adjacent
   5329    // cells and the table, row group, row
   5330    if (info.mNumTableCols == info.GetCellEndColIndex() + 1) {
   5331      // touches iEnd edge of table
   5332      uint8_t idxIEnd = static_cast<uint8_t>(LogicalSide::IEnd);
   5333      if (!tableBorderReset[idxIEnd]) {
   5334        tableInfo.ResetTableIEndBorderWidth();
   5335        tableBorderReset[idxIEnd] = true;
   5336      }
   5337      info.mCurrentRowFrame = nullptr;
   5338      bool reset = false;
   5339      for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
   5340           rowB++) {
   5341        info.IncrementRow(rowB == info.mRowIndex);
   5342        BCCellBorder currentBorder = info.GetIEndEdgeBorder();
   5343        // Update/store the bStart-iEnd & bEnd-iEnd corners. Note that we
   5344        // overwrite all corner information to the end of the column span.
   5345        BCCornerInfo& bStartIEndCorner =
   5346            (0 == rowB) ? bStartCorners[info.GetCellEndColIndex() + 1]
   5347                        : bEndCorners[info.GetCellEndColIndex() + 1];
   5348        bStartIEndCorner.Update(LogicalSide::BEnd, currentBorder);
   5349        tableCellMap->SetBCBorderCorner(
   5350            LogicalCorner::BStartIEnd, *iter.mCellMap, iter.mRowGroupStart,
   5351            rowB, info.GetCellEndColIndex(),
   5352            LogicalSide(bStartIEndCorner.ownerSide), bStartIEndCorner.subWidth,
   5353            bStartIEndCorner.bevel);
   5354        BCCornerInfo& bEndIEndCorner =
   5355            bEndCorners[info.GetCellEndColIndex() + 1];
   5356        bEndIEndCorner.Set(LogicalSide::BStart, currentBorder);
   5357        tableCellMap->SetBCBorderCorner(
   5358            LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
   5359            info.GetCellEndColIndex(), LogicalSide(bEndIEndCorner.ownerSide),
   5360            bEndIEndCorner.subWidth, bEndIEndCorner.bevel);
   5361        // update lastBlockDirBorders and see if a new segment starts
   5362        bool startSeg = SetBorder(
   5363            currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
   5364        // store the border segment in the cell map and update cellBorders
   5365        tableCellMap->SetBCBorderEdge(
   5366            LogicalSide::IEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
   5367            info.GetCellEndColIndex(), 1, currentBorder.owner,
   5368            currentBorder.width, startSeg);
   5369        // Set border width at inline-end (table-wide and for the cell), but
   5370        // only if it's the largest we've encountered.
   5371        tableInfo.SetTableIEndBorderWidth(currentBorder.width);
   5372        if (!reset) {
   5373          info.ResetIEndBorderWidths();
   5374          reset = true;
   5375        }
   5376        info.SetIEndBorderWidths(currentBorder.width);
   5377      }
   5378    } else {
   5379      // Cell entries, but not on the block-end side of the entire table.
   5380      int32_t segLength = 0;
   5381      BCMapCellInfo ajaInfo(this);
   5382      BCMapCellInfo priorAjaInfo(this);
   5383      bool reset = false;
   5384      for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
   5385           rowB += segLength) {
   5386        // Grab the cell adjacent to our inline-end.
   5387        iter.PeekIEnd(info, rowB, ajaInfo);
   5388        BCCellBorder currentBorder = info.GetIEndInternalBorder();
   5389        BCCellBorder adjacentBorder = ajaInfo.GetIStartInternalBorder();
   5390        currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
   5391                                       adjacentBorder, !INLINE_DIR);
   5392 
   5393        segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowB);
   5394        segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowB);
   5395 
   5396        // update lastBlockDirBorders and see if a new segment starts
   5397        bool startSeg = SetBorder(
   5398            currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
   5399        // store the border segment in the cell map and update cellBorders
   5400        if (info.GetCellEndColIndex() < damageArea.EndCol() &&
   5401            rowB >= damageArea.StartRow() && rowB < damageArea.EndRow()) {
   5402          tableCellMap->SetBCBorderEdge(
   5403              LogicalSide::IEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
   5404              info.GetCellEndColIndex(), segLength, currentBorder.owner,
   5405              currentBorder.width, startSeg);
   5406          if (!reset) {
   5407            info.ResetIEndBorderWidths();
   5408            ajaInfo.ResetIStartBorderWidths();
   5409            reset = true;
   5410          }
   5411          info.SetIEndBorderWidths(currentBorder.width);
   5412          ajaInfo.SetIStartBorderWidths(currentBorder.width);
   5413        }
   5414        // Does the block-start inline-end corner hit the inline-end adjacent
   5415        // cell that wouldn't have an inline border? e.g.
   5416        //
   5417        // o-----------o---------------o
   5418        // |           |               |
   5419        // o-----------x Adjacent cell o
   5420        // | This Cell |   (rowspan)   |
   5421        // o-----------o---------------o
   5422        bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) &&
   5423                              (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
   5424        BCCornerInfo* bStartIEndCorner =
   5425            ((0 == rowB) || hitsSpanOnIEnd)
   5426                ? &bStartCorners[info.GetCellEndColIndex() + 1]
   5427                : &bEndCorners[info.GetCellEndColIndex() +
   5428                               1];  // From previous row.
   5429        bStartIEndCorner->Update(LogicalSide::BEnd, currentBorder);
   5430        // If this is a rowspan, need to consider if this "corner" is generating
   5431        // an inline segment for the adjacent cell. e.g.
   5432        //
   5433        // o--------------o----o
   5434        // |              |    |
   5435        // o              x----o
   5436        // | (This "row") |    |
   5437        // o--------------o----o
   5438        if (rowB != info.mRowIndex) {
   5439          currentBorder = priorAjaInfo.GetBEndInternalBorder();
   5440          BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder();
   5441          currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
   5442                                         adjacentBorder, INLINE_DIR);
   5443          bStartIEndCorner->Update(LogicalSide::IEnd, currentBorder);
   5444        }
   5445        // Check that the spanned area is inside of the invalidation area
   5446        if (info.GetCellEndColIndex() < damageArea.EndCol() &&
   5447            rowB >= damageArea.StartRow()) {
   5448          if (0 != rowB) {
   5449            // Ok, actually store the information
   5450            tableCellMap->SetBCBorderCorner(
   5451                LogicalCorner::BStartIEnd, *iter.mCellMap, iter.mRowGroupStart,
   5452                rowB, info.GetCellEndColIndex(),
   5453                LogicalSide(bStartIEndCorner->ownerSide),
   5454                bStartIEndCorner->subWidth, bStartIEndCorner->bevel);
   5455          }
   5456          // Propagate this segment down the rowspan
   5457          for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) {
   5458            tableCellMap->SetBCBorderCorner(
   5459                LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart,
   5460                rX, info.GetCellEndColIndex(),
   5461                LogicalSide(bStartIEndCorner->ownerSide),
   5462                bStartIEndCorner->subWidth, false);
   5463          }
   5464        }
   5465        hitsSpanOnIEnd =
   5466            (rowB + segLength < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
   5467        BCCornerInfo& bEndIEndCorner =
   5468            (hitsSpanOnIEnd) ? bStartCorners[info.GetCellEndColIndex() + 1]
   5469                             : bEndCorners[info.GetCellEndColIndex() + 1];
   5470        bEndIEndCorner.Set(LogicalSide::BStart, currentBorder);
   5471        priorAjaInfo = ajaInfo;
   5472      }
   5473    }
   5474    for (int32_t colIdx = info.mColIndex + 1;
   5475         colIdx <= info.GetCellEndColIndex(); colIdx++) {
   5476      lastBlockDirBorders[colIdx].Reset(0, 1);
   5477    }
   5478 
   5479    // find the dominant border considering the cell's bEnd border, adjacent
   5480    // cells and the table, row group, row
   5481    if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) {
   5482      // touches bEnd edge of table
   5483      uint8_t idxBEnd = static_cast<uint8_t>(LogicalSide::BEnd);
   5484      if (!tableBorderReset[idxBEnd]) {
   5485        tableInfo.ResetTableBEndBorderWidth();
   5486        tableBorderReset[idxBEnd] = true;
   5487      }
   5488      bool reset = false;
   5489      for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
   5490           colIdx++) {
   5491        info.SetColumn(colIdx);
   5492        BCCellBorder currentBorder = info.GetBEndEdgeBorder();
   5493        BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx];
   5494        bEndIStartCorner.Update(LogicalSide::IEnd, currentBorder);
   5495        tableCellMap->SetBCBorderCorner(
   5496            LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart,
   5497            info.GetCellEndRowIndex(), colIdx,
   5498            LogicalSide(bEndIStartCorner.ownerSide), bEndIStartCorner.subWidth,
   5499            bEndIStartCorner.bevel);
   5500        BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + 1];
   5501        bEndIEndCorner.Update(LogicalSide::IStart, currentBorder);
   5502        // Store the block-end inline-end corner if it also is the block-end
   5503        // inline-end of the overall table.
   5504        if (info.mNumTableCols == colIdx + 1) {
   5505          tableCellMap->SetBCBorderCorner(
   5506              LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart,
   5507              info.GetCellEndRowIndex(), colIdx,
   5508              LogicalSide(bEndIEndCorner.ownerSide), bEndIEndCorner.subWidth,
   5509              bEndIEndCorner.bevel, true);
   5510        }
   5511        // update lastBEndBorder and see if a new segment starts
   5512        bool startSeg =
   5513            SetInlineDirBorder(currentBorder, bEndIStartCorner, lastBEndBorder);
   5514        if (!startSeg) {
   5515          // make sure that we did not compare apples to oranges i.e. the
   5516          // current border should be a continuation of the lastBEndBorder,
   5517          // as it is a bEnd border
   5518          // add 1 to the info.GetCellEndRowIndex()
   5519          startSeg =
   5520              (lastBEndBorder.rowIndex != (info.GetCellEndRowIndex() + 1));
   5521        }
   5522        // store the border segment in the cell map and update cellBorders
   5523        tableCellMap->SetBCBorderEdge(
   5524            LogicalSide::BEnd, *iter.mCellMap, iter.mRowGroupStart,
   5525            info.GetCellEndRowIndex(), colIdx, 1, currentBorder.owner,
   5526            currentBorder.width, startSeg);
   5527        // update lastBEndBorders
   5528        lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
   5529        lastBEndBorder.rowSpan = info.mRowSpan;
   5530        lastBEndBorders[colIdx] = lastBEndBorder;
   5531 
   5532        // Set border width at block-end (table-wide and for the cell), but
   5533        // only if it's the largest we've encountered.
   5534        if (!reset) {
   5535          info.ResetBEndBorderWidths();
   5536          reset = true;
   5537        }
   5538        info.SetBEndBorderWidths(currentBorder.width);
   5539        tableInfo.SetTableBEndBorderWidth(currentBorder.width);
   5540      }
   5541    } else {
   5542      int32_t segLength = 0;
   5543      BCMapCellInfo ajaInfo(this);
   5544      bool reset = false;
   5545      for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
   5546           colIdx += segLength) {
   5547        // Grab the cell adjacent to our block-end.
   5548        iter.PeekBEnd(info, colIdx, ajaInfo);
   5549        BCCellBorder currentBorder = info.GetBEndInternalBorder();
   5550        BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder();
   5551        currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
   5552                                       adjacentBorder, INLINE_DIR);
   5553        segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colIdx);
   5554        segLength =
   5555            std::min(segLength, info.mColIndex + info.mColSpan - colIdx);
   5556 
   5557        BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx];
   5558        // e.g.
   5559        // o--o----------o
   5560        // |  | This col |
   5561        // o--x----------o
   5562        // |  Adjacent   |
   5563        // o--o----------o
   5564        bool hitsSpanBelow = (colIdx > ajaInfo.mColIndex) &&
   5565                             (colIdx < ajaInfo.mColIndex + ajaInfo.mColSpan);
   5566        bool update = true;
   5567        if (colIdx == info.mColIndex && colIdx > damageArea.StartCol()) {
   5568          int32_t prevRowIndex = lastBEndBorders[colIdx - 1].rowIndex;
   5569          if (prevRowIndex > info.GetCellEndRowIndex() + 1) {
   5570            // hits a rowspan on the iEnd side
   5571            update = false;
   5572            // the corner was taken care of during the cell on the iStart side
   5573          } else if (prevRowIndex < info.GetCellEndRowIndex() + 1) {
   5574            // spans below the cell to the iStart side
   5575            bStartCorners[colIdx] = bEndIStartCorner;
   5576            bEndIStartCorner.Set(LogicalSide::IEnd, currentBorder);
   5577            update = false;
   5578          }
   5579        }
   5580        if (update) {
   5581          bEndIStartCorner.Update(LogicalSide::IEnd, currentBorder);
   5582        }
   5583        // Check that the spanned area is inside of the invalidation area
   5584        if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
   5585            colIdx >= damageArea.StartCol()) {
   5586          if (hitsSpanBelow) {
   5587            tableCellMap->SetBCBorderCorner(
   5588                LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart,
   5589                info.GetCellEndRowIndex(), colIdx,
   5590                LogicalSide(bEndIStartCorner.ownerSide),
   5591                bEndIStartCorner.subWidth, bEndIStartCorner.bevel);
   5592          }
   5593          // Propagate this segment down the colspan
   5594          for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) {
   5595            BCCornerInfo& corner = bEndCorners[c];
   5596            corner.Set(LogicalSide::IEnd, currentBorder);
   5597            tableCellMap->SetBCBorderCorner(
   5598                LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart,
   5599                info.GetCellEndRowIndex(), c, LogicalSide(corner.ownerSide),
   5600                corner.subWidth, false);
   5601          }
   5602        }
   5603        // update lastBEndBorders and see if a new segment starts
   5604        bool startSeg =
   5605            SetInlineDirBorder(currentBorder, bEndIStartCorner, lastBEndBorder);
   5606        if (!startSeg) {
   5607          // make sure that we did not compare apples to oranges i.e. the
   5608          // current border should be a continuation of the lastBEndBorder,
   5609          // as it is a bEnd border
   5610          // add 1 to the info.GetCellEndRowIndex()
   5611          startSeg = (lastBEndBorder.rowIndex != info.GetCellEndRowIndex() + 1);
   5612        }
   5613        lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
   5614        lastBEndBorder.rowSpan = info.mRowSpan;
   5615        for (int32_t c = colIdx; c < colIdx + segLength; c++) {
   5616          lastBEndBorders[c] = lastBEndBorder;
   5617        }
   5618 
   5619        // store the border segment the cell map and update cellBorders
   5620        if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
   5621            colIdx >= damageArea.StartCol() && colIdx < damageArea.EndCol()) {
   5622          tableCellMap->SetBCBorderEdge(
   5623              LogicalSide::BEnd, *iter.mCellMap, iter.mRowGroupStart,
   5624              info.GetCellEndRowIndex(), colIdx, segLength, currentBorder.owner,
   5625              currentBorder.width, startSeg);
   5626 
   5627          if (!reset) {
   5628            info.ResetBEndBorderWidths();
   5629            ajaInfo.ResetBStartBorderWidths();
   5630            reset = true;
   5631          }
   5632          info.SetBEndBorderWidths(currentBorder.width);
   5633          ajaInfo.SetBStartBorderWidths(currentBorder.width);
   5634        }
   5635        // update bEnd-iEnd corner
   5636        BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + segLength];
   5637        bEndIEndCorner.Update(LogicalSide::IStart, currentBorder);
   5638      }
   5639    }
   5640    // o------o------o
   5641    // |  c1  |      |
   5642    // o------o  c2  o
   5643    // |  c3  |      |
   5644    // o--e1--o--e2--o
   5645    // We normally join edges of successive block-end inline segments by
   5646    // consulting the previous segment; however, cell c2's block-end inline
   5647    // segment e2 is processed before e1, so we need to process such joins
   5648    // out-of-band here, when we're processing c3.
   5649    const auto nextColIndex = info.GetCellEndColIndex() + 1;
   5650    if ((info.mNumTableCols != nextColIndex) &&
   5651        (lastBEndBorders[nextColIndex].rowSpan > 1) &&
   5652        (lastBEndBorders[nextColIndex].rowIndex ==
   5653         info.GetCellEndRowIndex() + 1)) {
   5654      BCCornerInfo& corner = bEndCorners[nextColIndex];
   5655      if (!IsBlock(LogicalSide(corner.ownerSide))) {
   5656        // not a block-dir owner
   5657        BCCellBorder& thisBorder = lastBEndBorder;
   5658        BCCellBorder& nextBorder = lastBEndBorders[info.mColIndex + 1];
   5659        if ((thisBorder.color == nextBorder.color) &&
   5660            (thisBorder.width == nextBorder.width) &&
   5661            (thisBorder.style == nextBorder.style)) {
   5662          // set the flag on the next border indicating it is not the start of a
   5663          // new segment
   5664          if (iter.mCellMap) {
   5665            tableCellMap->ResetBStartStart(
   5666                LogicalSide::BEnd, *iter.mCellMap, iter.mRowGroupStart,
   5667                info.GetCellEndRowIndex(), nextColIndex);
   5668          }
   5669        }
   5670      }
   5671    }
   5672  }  // for (iter.First(info); info.mCell; iter.Next(info)) {
   5673  // reset the bc flag and damage area
   5674  SetNeedToCalcBCBorders(false);
   5675  propData->mDamageArea = TableArea(0, 0, 0, 0);
   5676 #ifdef DEBUG_TABLE_CELLMAP
   5677  mCellMap->Dump();
   5678 #endif
   5679 }
   5680 
   5681 class BCPaintBorderIterator;
   5682 
   5683 struct BCBorderParameters {
   5684  StyleBorderStyle mBorderStyle;
   5685  nscolor mBorderColor;
   5686  nsRect mBorderRect;
   5687  mozilla::Side mStartBevelSide;
   5688  nscoord mStartBevelOffset;
   5689  mozilla::Side mEndBevelSide;
   5690  nscoord mEndBevelOffset;
   5691  bool mBackfaceIsVisible;
   5692 
   5693  bool NeedToBevel() const {
   5694    if (!mStartBevelOffset && !mEndBevelOffset) {
   5695      return false;
   5696    }
   5697 
   5698    if (mBorderStyle == StyleBorderStyle::Dashed ||
   5699        mBorderStyle == StyleBorderStyle::Dotted) {
   5700      return false;
   5701    }
   5702 
   5703    return true;
   5704  }
   5705 };
   5706 
   5707 struct BCBlockDirSeg {
   5708  BCBlockDirSeg();
   5709 
   5710  void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner,
   5711             nscoord aBlockSegISize, nscoord aInlineSegBSize,
   5712             Maybe<nscoord> aEmptyRowEndSize);
   5713 
   5714  void Initialize(BCPaintBorderIterator& aIter);
   5715  void GetBEndCorner(BCPaintBorderIterator& aIter, nscoord aInlineSegBSize);
   5716 
   5717  Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter,
   5718                                                  nscoord aInlineSegBSize);
   5719  void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget,
   5720             nscoord aInlineSegBSize);
   5721  void CreateWebRenderCommands(BCPaintBorderIterator& aIter,
   5722                               nscoord aInlineSegBSize,
   5723                               wr::DisplayListBuilder& aBuilder,
   5724                               const layers::StackingContextHelper& aSc,
   5725                               const nsPoint& aPt);
   5726  void AdvanceOffsetB();
   5727  void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
   5728 
   5729  union {
   5730    nsTableColFrame* mCol;
   5731    int32_t mColWidth;
   5732  };
   5733  nscoord mOffsetI;  // i-offset with respect to the table edge
   5734  nscoord mOffsetB;  // b-offset with respect to the table edge
   5735  nscoord mLength;   // block-dir length including corners
   5736  nscoord mWidth;    // thickness
   5737 
   5738  nsTableCellFrame* mAjaCell;    // previous sibling to the first cell
   5739                                 // where the segment starts, it can be
   5740                                 // the owner of a segment
   5741  nsTableCellFrame* mFirstCell;  // cell at the start of the segment
   5742  nsTableRowGroupFrame*
   5743      mFirstRowGroup;           // row group at the start of the segment
   5744  nsTableRowFrame* mFirstRow;   // row at the start of the segment
   5745  nsTableCellFrame* mLastCell;  // cell at the current end of the
   5746                                // segment
   5747 
   5748  uint8_t mOwner;                // owner of the border, defines the
   5749                                 // style
   5750  LogicalSide mBStartBevelSide;  // direction to bevel at the bStart
   5751  nscoord mBStartBevelOffset;    // how much to bevel at the bStart
   5752  nscoord mBEndInlineSegBSize;   // bSize of the crossing
   5753                                 // inline-dir border
   5754  nscoord mBEndOffset;           // how much longer is the segment due
   5755                                 // to the inline-dir border, by this
   5756                                 // amount the next segment needs to be
   5757                                 // shifted.
   5758  bool mIsBEndBevel;             // should we bevel at the bEnd
   5759 };
   5760 
   5761 struct BCInlineDirSeg {
   5762  BCInlineDirSeg();
   5763 
   5764  void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner,
   5765             nscoord aBEndBlockSegISize, nscoord aInlineSegBSize);
   5766  void GetIEndCorner(BCPaintBorderIterator& aIter, nscoord aIStartSegISize);
   5767  void AdvanceOffsetI();
   5768  void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
   5769  Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter);
   5770  void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget);
   5771  void CreateWebRenderCommands(BCPaintBorderIterator& aIter,
   5772                               wr::DisplayListBuilder& aBuilder,
   5773                               const layers::StackingContextHelper& aSc,
   5774                               const nsPoint& aPt);
   5775 
   5776  nscoord mOffsetI;              // i-offset with respect to the table edge
   5777  nscoord mOffsetB;              // b-offset with respect to the table edge
   5778  nscoord mLength;               // inline-dir length including corners
   5779  nscoord mWidth;                // border thickness
   5780  nscoord mIStartBevelOffset;    // how much to bevel at the iStart
   5781  LogicalSide mIStartBevelSide;  // direction to bevel at the iStart
   5782  bool mIsIEndBevel;             // should we bevel at the iEnd end
   5783  nscoord mIEndBevelOffset;      // how much to bevel at the iEnd
   5784  LogicalSide mIEndBevelSide;    // direction to bevel at the iEnd
   5785  nscoord mEndOffset;            // how much longer is the segment due
   5786                                 // to the block-dir border, by this
   5787                                 // amount the next segment needs to be
   5788                                 // shifted.
   5789  uint8_t mOwner;                // owner of the border, defines the
   5790                                 // style
   5791  nsTableCellFrame* mFirstCell;  // cell at the start of the segment
   5792  nsTableCellFrame* mAjaCell;    // neighboring cell to the first cell
   5793                                 // where the segment starts, it can be
   5794                                 // the owner of a segment
   5795 };
   5796 
   5797 struct BCPaintData {
   5798  explicit BCPaintData(DrawTarget& aDrawTarget) : mDrawTarget(aDrawTarget) {}
   5799 
   5800  DrawTarget& mDrawTarget;
   5801 };
   5802 
   5803 struct BCCreateWebRenderCommandsData {
   5804  BCCreateWebRenderCommandsData(wr::DisplayListBuilder& aBuilder,
   5805                                const layers::StackingContextHelper& aSc,
   5806                                const nsPoint& aOffsetToReferenceFrame)
   5807      : mBuilder(aBuilder),
   5808        mSc(aSc),
   5809        mOffsetToReferenceFrame(aOffsetToReferenceFrame) {}
   5810 
   5811  wr::DisplayListBuilder& mBuilder;
   5812  const layers::StackingContextHelper& mSc;
   5813  const nsPoint& mOffsetToReferenceFrame;
   5814 };
   5815 
   5816 struct BCPaintBorderAction {
   5817  explicit BCPaintBorderAction(DrawTarget& aDrawTarget)
   5818      : mMode(Mode::Paint), mPaintData(aDrawTarget) {}
   5819 
   5820  BCPaintBorderAction(wr::DisplayListBuilder& aBuilder,
   5821                      const layers::StackingContextHelper& aSc,
   5822                      const nsPoint& aOffsetToReferenceFrame)
   5823      : mMode(Mode::CreateWebRenderCommands),
   5824        mCreateWebRenderCommandsData(aBuilder, aSc, aOffsetToReferenceFrame) {}
   5825 
   5826  ~BCPaintBorderAction() {
   5827    // mCreateWebRenderCommandsData is in a union which means the destructor
   5828    // wouldn't be called when BCPaintBorderAction get destroyed. So call the
   5829    // destructor here explicitly.
   5830    if (mMode == Mode::CreateWebRenderCommands) {
   5831      mCreateWebRenderCommandsData.~BCCreateWebRenderCommandsData();
   5832    }
   5833  }
   5834 
   5835  enum class Mode {
   5836    Paint,
   5837    CreateWebRenderCommands,
   5838  };
   5839 
   5840  Mode mMode;
   5841 
   5842  union {
   5843    BCPaintData mPaintData;
   5844    BCCreateWebRenderCommandsData mCreateWebRenderCommandsData;
   5845  };
   5846 };
   5847 
   5848 // Iterates over borders (iStart border, corner, bStart border) in the cell map
   5849 // within a damage area from iStart to iEnd, bStart to bEnd. All members are in
   5850 // terms of the 1st in flow frames, except where suffixed by InFlow.
   5851 class BCPaintBorderIterator {
   5852 public:
   5853  explicit BCPaintBorderIterator(nsTableFrame* aTable);
   5854  void Reset();
   5855 
   5856  /**
   5857   * Determine the damage area in terms of rows and columns and finalize
   5858   * mInitialOffsetI and mInitialOffsetB.
   5859   * @param aDirtyRect - dirty rect in table coordinates
   5860   * @return - true if we need to paint something given dirty rect
   5861   */
   5862  bool SetDamageArea(const nsRect& aDamageRect);
   5863  void First();
   5864  void Next();
   5865  void AccumulateOrDoActionInlineDirSegment(BCPaintBorderAction& aAction);
   5866  void AccumulateOrDoActionBlockDirSegment(BCPaintBorderAction& aAction);
   5867  void ResetVerInfo();
   5868  void StoreColumnWidth(int32_t aIndex);
   5869  bool BlockDirSegmentOwnsCorner();
   5870 
   5871  nsTableFrame* mTable;
   5872  nsTableFrame* mTableFirstInFlow;
   5873  nsTableCellMap* mTableCellMap;
   5874  nsCellMap* mCellMap;
   5875  WritingMode mTableWM;
   5876  nsTableFrame::RowGroupArray mRowGroups;
   5877 
   5878  nsTableRowGroupFrame* mPrevRg;
   5879  nsTableRowGroupFrame* mRg;
   5880  bool mIsRepeatedHeader;
   5881  bool mIsRepeatedFooter;
   5882  nsTableRowGroupFrame* mStartRg;   // first row group in the damagearea
   5883  int32_t mRgIndex;                 // current row group index in the
   5884                                    // mRowgroups array
   5885  int32_t mFifRgFirstRowIndex;      // start row index of the first in
   5886                                    // flow of the row group
   5887  int32_t mRgFirstRowIndex;         // row index of the first row in the
   5888                                    // row group
   5889  int32_t mRgLastRowIndex;          // row index of the last row in the row
   5890                                    // group
   5891  int32_t mNumTableRows;            // number of rows in the table and all
   5892                                    // continuations
   5893  int32_t mNumTableCols;            // number of columns in the table
   5894  int32_t mColIndex;                // with respect to the table
   5895  int32_t mRowIndex;                // with respect to the table
   5896  int32_t mRepeatedHeaderRowIndex;  // row index in a repeated
   5897                                    // header, it's equivalent to
   5898                                    // mRowIndex when we're in a repeated
   5899                                    // header, and set to the last row
   5900                                    // index of a repeated header when
   5901                                    // we're not
   5902  bool mIsNewRow;
   5903  bool mAtEnd;  // the iterator cycled over all
   5904                // borders
   5905  nsTableRowFrame* mPrevRow;
   5906  nsTableRowFrame* mRow;
   5907  nsTableRowFrame* mStartRow;  // first row in a inside the damagearea
   5908 
   5909  // cell properties
   5910  nsTableCellFrame* mPrevCell;
   5911  nsTableCellFrame* mCell;
   5912  BCCellData* mPrevCellData;
   5913  BCCellData* mCellData;
   5914  BCData* mBCData;
   5915 
   5916  bool IsTableBStartMost() {
   5917    return (mRowIndex == 0) && !mTable->GetPrevInFlow();
   5918  }
   5919  bool IsTableIEndMost() { return (mColIndex >= mNumTableCols); }
   5920  bool IsTableBEndMost() {
   5921    return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();
   5922  }
   5923  bool IsTableIStartMost() { return (mColIndex == 0); }
   5924  bool IsDamageAreaBStartMost() const {
   5925    return mRowIndex == mDamageArea.StartRow();
   5926  }
   5927  bool IsDamageAreaIEndMost() const {
   5928    return mColIndex >= mDamageArea.EndCol();
   5929  }
   5930  bool IsDamageAreaBEndMost() const {
   5931    return mRowIndex >= mDamageArea.EndRow();
   5932  }
   5933  bool IsDamageAreaIStartMost() const {
   5934    return mColIndex == mDamageArea.StartCol();
   5935  }
   5936  int32_t GetRelativeColIndex() const {
   5937    return mColIndex - mDamageArea.StartCol();
   5938  }
   5939 
   5940  TableArea mDamageArea;  // damageArea in cellmap coordinates
   5941  bool IsAfterRepeatedHeader() {
   5942    return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1));
   5943  }
   5944  bool StartRepeatedFooter() const {
   5945    return mIsRepeatedFooter && mRowIndex == mRgFirstRowIndex &&
   5946           mRowIndex != mDamageArea.StartRow();
   5947  }
   5948 
   5949  nscoord mInitialOffsetI;  // offsetI of the first border with
   5950                            // respect to the table
   5951  nscoord mInitialOffsetB;  // offsetB of the first border with
   5952                            // respect to the table
   5953  nscoord mNextOffsetB;     // offsetB of the next segment
   5954  // this array is used differently when
   5955  // inline-dir and block-dir borders are drawn
   5956  // When inline-dir border are drawn we cache
   5957  // the column widths and the width of the
   5958  // block-dir borders that arrive from bStart
   5959  // When we draw block-dir borders we store
   5960  // lengths and width for block-dir borders
   5961  // before they are drawn while we  move over
   5962  // the columns in the damage area
   5963  // It has one more elements than columns are
   5964  // in the table.
   5965  UniquePtr<BCBlockDirSeg[]> mBlockDirInfo;
   5966  BCInlineDirSeg mInlineSeg;    // the inline-dir segment while we
   5967                                // move over the colums
   5968  nscoord mPrevInlineSegBSize;  // the bSize of the previous
   5969                                // inline-dir border
   5970 
   5971 private:
   5972  bool SetNewRow(nsTableRowFrame* aRow = nullptr);
   5973  bool SetNewRowGroup();
   5974  void SetNewData(int32_t aRowIndex, int32_t aColIndex);
   5975 };
   5976 
   5977 BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable)
   5978    : mTable(aTable),
   5979      mTableFirstInFlow(static_cast<nsTableFrame*>(aTable->FirstInFlow())),
   5980      mTableCellMap(aTable->GetCellMap()),
   5981      mCellMap(nullptr),
   5982      mTableWM(aTable->Style()),
   5983      mRowGroups(aTable->OrderedRowGroups()),
   5984      mPrevRg(nullptr),
   5985      mRg(nullptr),
   5986      mIsRepeatedHeader(false),
   5987      mIsRepeatedFooter(false),
   5988      mStartRg(nullptr),
   5989      mRgIndex(0),
   5990      mFifRgFirstRowIndex(0),
   5991      mRgFirstRowIndex(0),
   5992      mRgLastRowIndex(0),
   5993      mColIndex(0),
   5994      mRowIndex(0),
   5995      mIsNewRow(false),
   5996      mAtEnd(false),
   5997      mPrevRow(nullptr),
   5998      mRow(nullptr),
   5999      mStartRow(nullptr),
   6000      mPrevCell(nullptr),
   6001      mCell(nullptr),
   6002      mPrevCellData(nullptr),
   6003      mCellData(nullptr),
   6004      mBCData(nullptr),
   6005      mInitialOffsetI(0),
   6006      mNextOffsetB(0),
   6007      mPrevInlineSegBSize(0) {
   6008  MOZ_ASSERT(mTable->IsBorderCollapse(),
   6009             "Why are we here if the table is not border-collapsed?");
   6010 
   6011  const LogicalMargin bp = mTable->GetOuterBCBorder(mTableWM);
   6012  // block position of first row in damage area
   6013  mInitialOffsetB = mTable->GetPrevInFlow() ? 0 : bp.BStart(mTableWM);
   6014  mNumTableRows = mTable->GetRowCount();
   6015  mNumTableCols = mTable->GetColCount();
   6016 
   6017  // initialize to a non existing index
   6018  mRepeatedHeaderRowIndex = -99;
   6019 }
   6020 
   6021 bool BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) {
   6022  nsSize containerSize = mTable->GetSize();
   6023  LogicalRect dirtyRect(mTableWM, aDirtyRect, containerSize);
   6024  uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex;
   6025  startRowIndex = endRowIndex = startColIndex = endColIndex = 0;
   6026  bool done = false;
   6027  bool haveIntersect = false;
   6028  // find startRowIndex, endRowIndex
   6029  nscoord rowB = mInitialOffsetB;
   6030  for (uint32_t rgIdx = 0; rgIdx < mRowGroups.Length() && !done; rgIdx++) {
   6031    nsTableRowGroupFrame* rgFrame = mRowGroups[rgIdx];
   6032    for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
   6033         rowFrame = rowFrame->GetNextRow()) {
   6034      // get the row rect relative to the table rather than the row group
   6035      nscoord rowBSize = rowFrame->BSize(mTableWM);
   6036      const nscoord onePx = mTable->PresContext()->DevPixelsToAppUnits(1);
   6037      if (haveIntersect) {
   6038        // conservatively estimate the half border widths outside the row
   6039        nscoord borderHalf = mTable->GetPrevInFlow()
   6040                                 ? 0
   6041                                 : rowFrame->GetBStartBCBorderWidth() + onePx;
   6042 
   6043        if (dirtyRect.BEnd(mTableWM) >= rowB - borderHalf) {
   6044          nsTableRowFrame* fifRow =
   6045              static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
   6046          endRowIndex = fifRow->GetRowIndex();
   6047        } else {
   6048          done = true;
   6049        }
   6050      } else {
   6051        // conservatively estimate the half border widths outside the row
   6052        nscoord borderHalf = mTable->GetNextInFlow()
   6053                                 ? 0
   6054                                 : rowFrame->GetBEndBCBorderWidth() + onePx;
   6055        if (rowB + rowBSize + borderHalf >= dirtyRect.BStart(mTableWM)) {
   6056          mStartRg = rgFrame;
   6057          mStartRow = rowFrame;
   6058          nsTableRowFrame* fifRow =
   6059              static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
   6060          startRowIndex = endRowIndex = fifRow->GetRowIndex();
   6061          haveIntersect = true;
   6062        } else {
   6063          mInitialOffsetB += rowBSize;
   6064        }
   6065      }
   6066      rowB += rowBSize;
   6067    }
   6068  }
   6069  mNextOffsetB = mInitialOffsetB;
   6070 
   6071  // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag
   6072  // XXX but I don't understand it, so not changing it for now
   6073  // table wrapper borders overflow the table, so the table might be
   6074  // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set
   6075  // on the table
   6076  if (!haveIntersect) {
   6077    return false;
   6078  }
   6079  // find startColIndex, endColIndex, startColX
   6080  haveIntersect = false;
   6081  if (0 == mNumTableCols) {
   6082    return false;
   6083  }
   6084 
   6085  LogicalMargin bp = mTable->GetOuterBCBorder(mTableWM);
   6086 
   6087  // inline position of first col in damage area
   6088  mInitialOffsetI = bp.IStart(mTableWM);
   6089 
   6090  nscoord x = 0;
   6091  int32_t colIdx;
   6092  for (colIdx = 0; colIdx != mNumTableCols; colIdx++) {
   6093    nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colIdx);
   6094    if (!colFrame) ABORT1(false);
   6095    const nscoord onePx = mTable->PresContext()->DevPixelsToAppUnits(1);
   6096    // get the col rect relative to the table rather than the col group
   6097    nscoord colISize = colFrame->ISize(mTableWM);
   6098    if (haveIntersect) {
   6099      // conservatively estimate the iStart half border width outside the col
   6100      nscoord iStartBorderHalf = colFrame->GetIStartBorderWidth() + onePx;
   6101      if (dirtyRect.IEnd(mTableWM) >= x - iStartBorderHalf) {
   6102        endColIndex = colIdx;
   6103      } else {
   6104        break;
   6105      }
   6106    } else {
   6107      // conservatively estimate the iEnd half border width outside the col
   6108      nscoord iEndBorderHalf = colFrame->GetIEndBorderWidth() + onePx;
   6109      if (x + colISize + iEndBorderHalf >= dirtyRect.IStart(mTableWM)) {
   6110        startColIndex = endColIndex = colIdx;
   6111        haveIntersect = true;
   6112      } else {
   6113        mInitialOffsetI += colISize;
   6114      }
   6115    }
   6116    x += colISize;
   6117  }
   6118  if (!haveIntersect) {
   6119    return false;
   6120  }
   6121  mDamageArea =
   6122      TableArea(startColIndex, startRowIndex,
   6123                1 + DeprecatedAbs<int32_t>(endColIndex - startColIndex),
   6124                1 + endRowIndex - startRowIndex);
   6125 
   6126  Reset();
   6127  mBlockDirInfo = MakeUnique<BCBlockDirSeg[]>(mDamageArea.ColCount() + 1);
   6128  return true;
   6129 }
   6130 
   6131 void BCPaintBorderIterator::Reset() {
   6132  mAtEnd = true;  // gets reset when First() is called
   6133  mRg = mStartRg;
   6134  mPrevRow = nullptr;
   6135  mRow = mStartRow;
   6136  mRowIndex = 0;
   6137  mColIndex = 0;
   6138  mRgIndex = -1;
   6139  mPrevCell = nullptr;
   6140  mCell = nullptr;
   6141  mPrevCellData = nullptr;
   6142  mCellData = nullptr;
   6143  mBCData = nullptr;
   6144  ResetVerInfo();
   6145 }
   6146 
   6147 /**
   6148 * Set the iterator data to a new cellmap coordinate
   6149 * @param aRowIndex - the row index
   6150 * @param aColIndex - the col index
   6151 */
   6152 void BCPaintBorderIterator::SetNewData(int32_t aY, int32_t aX) {
   6153  if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0();
   6154 
   6155  mColIndex = aX;
   6156  mRowIndex = aY;
   6157  mPrevCellData = mCellData;
   6158  if (IsTableIEndMost() && IsTableBEndMost()) {
   6159    mCell = nullptr;
   6160    mBCData = &mTableCellMap->mBCInfo->mBEndIEndCorner;
   6161  } else if (IsTableIEndMost()) {
   6162    mCellData = nullptr;
   6163    mBCData = &mTableCellMap->mBCInfo->mIEndBorders.ElementAt(aY);
   6164  } else if (IsTableBEndMost()) {
   6165    mCellData = nullptr;
   6166    mBCData = &mTableCellMap->mBCInfo->mBEndBorders.ElementAt(aX);
   6167  } else {
   6168    // We should have set mCellMap during SetNewRowGroup, but if we failed to
   6169    // find the appropriate map there, let's just give up.
   6170    // Bailing out here may leave us with some missing borders, but seems
   6171    // preferable to crashing. (Bug 1442018)
   6172    if (MOZ_UNLIKELY(!mCellMap)) {
   6173      ABORT0();
   6174    }
   6175    if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) {
   6176      mBCData = nullptr;
   6177      mCellData = (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex]
   6178                      .SafeElementAt(mColIndex);
   6179      if (mCellData) {
   6180        mBCData = &mCellData->mData;
   6181        if (!mCellData->IsOrig()) {
   6182          if (mCellData->IsRowSpan()) {
   6183            aY -= mCellData->GetRowSpanOffset();
   6184          }
   6185          if (mCellData->IsColSpan()) {
   6186            aX -= mCellData->GetColSpanOffset();
   6187          }
   6188          if ((aX >= 0) && (aY >= 0)) {
   6189            mCellData =
   6190                (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX];
   6191          }
   6192        }
   6193        if (mCellData->IsOrig()) {
   6194          mPrevCell = mCell;
   6195          mCell = mCellData->GetCellFrame();
   6196        }
   6197      }
   6198    }
   6199  }
   6200 }
   6201 
   6202 /**
   6203 * Set the iterator to a new row
   6204 * @param aRow - the new row frame, if null the iterator will advance to the
   6205 *               next row
   6206 */
   6207 bool BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow) {
   6208  mPrevRow = mRow;
   6209  mRow = (aRow) ? aRow : mRow->GetNextRow();
   6210  if (mRow) {
   6211    mIsNewRow = true;
   6212    mRowIndex = mRow->GetRowIndex();
   6213    mColIndex = mDamageArea.StartCol();
   6214    mPrevInlineSegBSize = 0;
   6215    if (mIsRepeatedHeader) {
   6216      mRepeatedHeaderRowIndex = mRowIndex;
   6217    }
   6218  } else {
   6219    mAtEnd = true;
   6220  }
   6221  return !mAtEnd;
   6222 }
   6223 
   6224 /**
   6225 * Advance the iterator to the next row group
   6226 */
   6227 bool BCPaintBorderIterator::SetNewRowGroup() {
   6228  mRgIndex++;
   6229 
   6230  mIsRepeatedHeader = false;
   6231  mIsRepeatedFooter = false;
   6232 
   6233  NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds");
   6234  if (uint32_t(mRgIndex) < mRowGroups.Length()) {
   6235    mPrevRg = mRg;
   6236    mRg = mRowGroups[mRgIndex];
   6237    nsTableRowGroupFrame* fifRg =
   6238        static_cast<nsTableRowGroupFrame*>(mRg->FirstInFlow());
   6239    mFifRgFirstRowIndex = fifRg->GetStartRowIndex();
   6240    mRgFirstRowIndex = mRg->GetStartRowIndex();
   6241    mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1;
   6242 
   6243    if (SetNewRow(mRg->GetFirstRow())) {
   6244      mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr);
   6245      if (!mCellMap) ABORT1(false);
   6246    }
   6247    if (mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) {
   6248      // if mRowGroup doesn't have a prev in flow, then it may be a repeated
   6249      // header or footer
   6250      const nsStyleDisplay* display = mRg->StyleDisplay();
   6251      if (mRowIndex == mDamageArea.StartRow()) {
   6252        mIsRepeatedHeader =
   6253            (mozilla::StyleDisplay::TableHeaderGroup == display->mDisplay);
   6254      } else {
   6255        mIsRepeatedFooter =
   6256            (mozilla::StyleDisplay::TableFooterGroup == display->mDisplay);
   6257      }
   6258    }
   6259  } else {
   6260    mAtEnd = true;
   6261  }
   6262  return !mAtEnd;
   6263 }
   6264 
   6265 /**
   6266 *  Move the iterator to the first position in the damageArea
   6267 */
   6268 void BCPaintBorderIterator::First() {
   6269  if (!mTable || mDamageArea.StartCol() >= mNumTableCols ||
   6270      mDamageArea.StartRow() >= mNumTableRows)
   6271    ABORT0();
   6272 
   6273  mAtEnd = false;
   6274 
   6275  uint32_t numRowGroups = mRowGroups.Length();
   6276  for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) {
   6277    nsTableRowGroupFrame* rowG = mRowGroups[rgY];
   6278    int32_t start = rowG->GetStartRowIndex();
   6279    int32_t end = start + rowG->GetRowCount() - 1;
   6280    if (mDamageArea.StartRow() >= start && mDamageArea.StartRow() <= end) {
   6281      mRgIndex = rgY - 1;  // SetNewRowGroup increments rowGroupIndex
   6282      if (SetNewRowGroup()) {
   6283        while (mRowIndex < mDamageArea.StartRow() && !mAtEnd) {
   6284          SetNewRow();
   6285        }
   6286        if (!mAtEnd) {
   6287          SetNewData(mDamageArea.StartRow(), mDamageArea.StartCol());
   6288        }
   6289      }
   6290      return;
   6291    }
   6292  }
   6293  mAtEnd = true;
   6294 }
   6295 
   6296 /**
   6297 * Advance the iterator to the next position
   6298 */
   6299 void BCPaintBorderIterator::Next() {
   6300  if (mAtEnd) ABORT0();
   6301  mIsNewRow = false;
   6302 
   6303  mColIndex++;
   6304  if (mColIndex > mDamageArea.EndCol()) {
   6305    mRowIndex++;
   6306    if (mRowIndex == mDamageArea.EndRow()) {
   6307      mColIndex = mDamageArea.StartCol();
   6308    } else if (mRowIndex < mDamageArea.EndRow()) {
   6309      if (mRowIndex <= mRgLastRowIndex) {
   6310        SetNewRow();
   6311      } else {
   6312        SetNewRowGroup();
   6313      }
   6314    } else {
   6315      mAtEnd = true;
   6316    }
   6317  }
   6318  if (!mAtEnd) {
   6319    SetNewData(mRowIndex, mColIndex);
   6320  }
   6321 }
   6322 
   6323 // XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine
   6324 // them
   6325 // XXX Update terminology from physical to logical
   6326 /** Compute the vertical offset of a vertical border segment
   6327 * @param aCornerOwnerSide - which side owns the corner
   6328 * @param aCornerSubWidth  - how wide is the nonwinning side of the corner
   6329 * @param aHorWidth        - how wide is the horizontal edge of the corner
   6330 * @param aIsStartOfSeg    - does this corner start a new segment
   6331 * @param aIsBevel         - is this corner beveled
   6332 * @return                 - offset in twips
   6333 */
   6334 static nscoord CalcVerCornerOffset(LogicalSide aCornerOwnerSide,
   6335                                   nscoord aCornerSubWidth, nscoord aHorWidth,
   6336                                   bool aIsStartOfSeg, bool aIsBevel) {
   6337  nscoord offset = 0;
   6338  // XXX These should be replaced with appropriate side-specific macros (which?)
   6339  nscoord smallHalf, largeHalf;
   6340  if (IsBlock(aCornerOwnerSide)) {
   6341    DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
   6342    if (aIsBevel) {
   6343      offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
   6344    } else {
   6345      offset =
   6346          (LogicalSide::BStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
   6347    }
   6348  } else {
   6349    DivideBCBorderSize(aHorWidth, smallHalf, largeHalf);
   6350    if (aIsBevel) {
   6351      offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
   6352    } else {
   6353      offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
   6354    }
   6355  }
   6356  return offset;
   6357 }
   6358 
   6359 /** Compute the horizontal offset of a horizontal border segment
   6360 * @param aCornerOwnerSide - which side owns the corner
   6361 * @param aCornerSubWidth  - how wide is the nonwinning side of the corner
   6362 * @param aVerWidth        - how wide is the vertical edge of the corner
   6363 * @param aIsStartOfSeg    - does this corner start a new segment
   6364 * @param aIsBevel         - is this corner beveled
   6365 * @return                 - offset in twips
   6366 */
   6367 static nscoord CalcHorCornerOffset(LogicalSide aCornerOwnerSide,
   6368                                   nscoord aCornerSubWidth, nscoord aVerWidth,
   6369                                   bool aIsStartOfSeg, bool aIsBevel) {
   6370  nscoord offset = 0;
   6371  // XXX These should be replaced with appropriate side-specific macros (which?)
   6372  nscoord smallHalf, largeHalf;
   6373  if (IsInline(aCornerOwnerSide)) {
   6374    DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
   6375    if (aIsBevel) {
   6376      offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
   6377    } else {
   6378      offset =
   6379          (LogicalSide::IStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
   6380    }
   6381  } else {
   6382    DivideBCBorderSize(aVerWidth, smallHalf, largeHalf);
   6383    if (aIsBevel) {
   6384      offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
   6385    } else {
   6386      offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
   6387    }
   6388  }
   6389  return offset;
   6390 }
   6391 
   6392 BCBlockDirSeg::BCBlockDirSeg()
   6393    : mFirstRowGroup(nullptr),
   6394      mFirstRow(nullptr),
   6395      mBEndInlineSegBSize(0),
   6396      mBEndOffset(0),
   6397      mIsBEndBevel(false) {
   6398  mCol = nullptr;
   6399  mFirstCell = mLastCell = mAjaCell = nullptr;
   6400  mOffsetI = mOffsetB = mLength = mWidth = mBStartBevelOffset = 0;
   6401  mBStartBevelSide = LogicalSide::BStart;
   6402  mOwner = eCellOwner;
   6403 }
   6404 
   6405 /**
   6406 * Start a new block-direction segment
   6407 * @param aIter         - iterator containing the structural information
   6408 * @param aBorderOwner  - determines the border style
   6409 * @param aBlockSegISize  - the width of segment
   6410 * @param aInlineSegBSize - the width of the inline-dir segment joining the
   6411 * corner at the start
   6412 */
   6413 void BCBlockDirSeg::Start(BCPaintBorderIterator& aIter,
   6414                          BCBorderOwner aBorderOwner, nscoord aBlockSegISize,
   6415                          nscoord aInlineSegBSize,
   6416                          Maybe<nscoord> aEmptyRowEndBSize) {
   6417  LogicalSide ownerSide = LogicalSide::BStart;
   6418  bool bevel = false;
   6419 
   6420  nscoord cornerSubWidth =
   6421      (aIter.mBCData) ? aIter.mBCData->GetCorner(ownerSide, bevel) : 0;
   6422 
   6423  bool bStartBevel = (aBlockSegISize > 0) ? bevel : false;
   6424  nscoord maxInlineSegBSize =
   6425      std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
   6426  nscoord offset = CalcVerCornerOffset(ownerSide, cornerSubWidth,
   6427                                       maxInlineSegBSize, true, bStartBevel);
   6428 
   6429  mBStartBevelOffset = bStartBevel ? maxInlineSegBSize : 0;
   6430  // XXX this assumes that only corners where 2 segments join can be beveled
   6431  mBStartBevelSide =
   6432      (aInlineSegBSize > 0) ? LogicalSide::IEnd : LogicalSide::IStart;
   6433  if (aEmptyRowEndBSize && *aEmptyRowEndBSize < offset) {
   6434    // This segment is starting from an empty row. This will require the the
   6435    // starting segment to overlap with the previously drawn segment, unless the
   6436    // empty row's size clears the overlap.
   6437    mOffsetB += *aEmptyRowEndBSize;
   6438  } else {
   6439    mOffsetB += offset;
   6440  }
   6441  mLength = -offset;
   6442  mWidth = aBlockSegISize;
   6443  mOwner = aBorderOwner;
   6444  mFirstCell = aIter.mCell;
   6445  mFirstRowGroup = aIter.mRg;
   6446  mFirstRow = aIter.mRow;
   6447  if (aIter.GetRelativeColIndex() > 0) {
   6448    mAjaCell = aIter.mBlockDirInfo[aIter.GetRelativeColIndex() - 1].mLastCell;
   6449  }
   6450 }
   6451 
   6452 /**
   6453 * Initialize the block-dir segments with information that will persist for any
   6454 * block-dir segment in this column
   6455 * @param aIter - iterator containing the structural information
   6456 */
   6457 void BCBlockDirSeg::Initialize(BCPaintBorderIterator& aIter) {
   6458  int32_t relColIndex = aIter.GetRelativeColIndex();
   6459  mCol = aIter.IsTableIEndMost()
   6460             ? aIter.mBlockDirInfo[relColIndex - 1].mCol
   6461             : aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex);
   6462  if (!mCol) ABORT0();
   6463  if (0 == relColIndex) {
   6464    mOffsetI = aIter.mInitialOffsetI;
   6465  }
   6466  // set mOffsetI for the next column
   6467  if (!aIter.IsDamageAreaIEndMost()) {
   6468    aIter.mBlockDirInfo[relColIndex + 1].mOffsetI =
   6469        mOffsetI + mCol->ISize(aIter.mTableWM);
   6470  }
   6471  mOffsetB = aIter.mInitialOffsetB;
   6472  mLastCell = aIter.mCell;
   6473 }
   6474 
   6475 /**
   6476 * Compute the offsets for the bEnd corner of a block-dir segment
   6477 * @param aIter           - iterator containing the structural information
   6478 * @param aInlineSegBSize - the width of the inline-dir segment joining the
   6479 * corner at the start
   6480 */
   6481 void BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator& aIter,
   6482                                  nscoord aInlineSegBSize) {
   6483  LogicalSide ownerSide = LogicalSide::BStart;
   6484  nscoord cornerSubWidth = 0;
   6485  bool bevel = false;
   6486  if (aIter.mBCData) {
   6487    cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
   6488  }
   6489  mIsBEndBevel = (mWidth > 0) ? bevel : false;
   6490  mBEndInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
   6491  mBEndOffset = CalcVerCornerOffset(ownerSide, cornerSubWidth,
   6492                                    mBEndInlineSegBSize, false, mIsBEndBevel);
   6493  mLength += mBEndOffset;
   6494 }
   6495 
   6496 Maybe<BCBorderParameters> BCBlockDirSeg::BuildBorderParameters(
   6497    BCPaintBorderIterator& aIter, nscoord aInlineSegBSize) {
   6498  BCBorderParameters result;
   6499 
   6500  // get the border style, color and paint the segment
   6501  LogicalSide side =
   6502      aIter.IsDamageAreaIEndMost() ? LogicalSide::IEnd : LogicalSide::IStart;
   6503  int32_t relColIndex = aIter.GetRelativeColIndex();
   6504  nsTableColFrame* col = mCol;
   6505  if (!col) ABORT1(Nothing());
   6506  nsTableCellFrame* cell = mFirstCell;  // ???
   6507  nsIFrame* owner = nullptr;
   6508  result.mBorderStyle = StyleBorderStyle::Solid;
   6509  result.mBorderColor = 0xFFFFFFFF;
   6510  result.mBackfaceIsVisible = true;
   6511 
   6512  switch (mOwner) {
   6513    case eTableOwner:
   6514      owner = aIter.mTable;
   6515      break;
   6516    case eAjaColGroupOwner:
   6517      side = LogicalSide::IEnd;
   6518      if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
   6519        col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
   6520      }
   6521      [[fallthrough]];
   6522    case eColGroupOwner:
   6523      if (col) {
   6524        owner = col->GetParent();
   6525      }
   6526      break;
   6527    case eAjaColOwner:
   6528      side = LogicalSide::IEnd;
   6529      if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
   6530        col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
   6531      }
   6532      [[fallthrough]];
   6533    case eColOwner:
   6534      owner = col;
   6535      break;
   6536    case eAjaRowGroupOwner:
   6537      NS_ERROR("a neighboring rowgroup can never own a vertical border");
   6538      [[fallthrough]];
   6539    case eRowGroupOwner:
   6540      NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
   6541                   "row group can own border only at table edge");
   6542      owner = mFirstRowGroup;
   6543      break;
   6544    case eAjaRowOwner:
   6545      NS_ERROR("program error");
   6546      [[fallthrough]];
   6547    case eRowOwner:
   6548      NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
   6549                   "row can own border only at table edge");
   6550      owner = mFirstRow;
   6551      break;
   6552    case eAjaCellOwner:
   6553      side = LogicalSide::IEnd;
   6554      cell = mAjaCell;
   6555      [[fallthrough]];
   6556    case eCellOwner:
   6557      owner = cell;
   6558      break;
   6559  }
   6560  if (owner) {
   6561    ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle,
   6562                        &result.mBorderColor);
   6563    result.mBackfaceIsVisible = !owner->BackfaceIsHidden();
   6564  }
   6565  nscoord smallHalf, largeHalf;
   6566  DivideBCBorderSize(mWidth, smallHalf, largeHalf);
   6567  LogicalRect segRect(aIter.mTableWM, mOffsetI - largeHalf, mOffsetB, mWidth,
   6568                      mLength);
   6569  nscoord bEndBevelOffset = mIsBEndBevel ? mBEndInlineSegBSize : 0;
   6570  LogicalSide bEndBevelSide =
   6571      (aInlineSegBSize > 0) ? LogicalSide::IEnd : LogicalSide::IStart;
   6572 
   6573  // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
   6574 
   6575  result.mBorderRect =
   6576      segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize());
   6577  // XXX For reversed vertical writing-modes (with direction:rtl), we need to
   6578  // invert physicalRect's y-position here, with respect to the table.
   6579  // However, it's not worth fixing the border positions here until the
   6580  // ordering of the table columns themselves is also fixed (bug 1180528).
   6581 
   6582  result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide);
   6583  result.mEndBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide);
   6584  result.mStartBevelOffset = mBStartBevelOffset;
   6585  result.mEndBevelOffset = bEndBevelOffset;
   6586  // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal)
   6587  // border segment need to be swapped because DrawTableBorderSegment will
   6588  // apply the 'start' bevel at the left edge, and 'end' at the right.
   6589  // (Note: In this case, startBevelSide/endBevelSide will usually both be
   6590  // "top" or "bottom". DrawTableBorderSegment works purely with physical
   6591  // coordinates, so it expects startBevelOffset to be the indentation-from-
   6592  // the-left for the "start" (left) end of the border-segment, and
   6593  // endBevelOffset is the indentation-from-the-right for the "end" (right)
   6594  // end of the border-segment. We've got them reversed, since our block dir
   6595  // is RTL, so we have to swap them here.)
   6596  if (aIter.mTableWM.IsVerticalRL()) {
   6597    std::swap(result.mStartBevelSide, result.mEndBevelSide);
   6598    std::swap(result.mStartBevelOffset, result.mEndBevelOffset);
   6599  }
   6600 
   6601  return Some(result);
   6602 }
   6603 
   6604 /**
   6605 * Paint the block-dir segment
   6606 * @param aIter           - iterator containing the structural information
   6607 * @param aDrawTarget     - the draw target
   6608 * @param aInlineSegBSize - the width of the inline-dir segment joining the
   6609 *                          corner at the start
   6610 */
   6611 void BCBlockDirSeg::Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget,
   6612                          nscoord aInlineSegBSize) {
   6613  Maybe<BCBorderParameters> param =
   6614      BuildBorderParameters(aIter, aInlineSegBSize);
   6615  if (param.isNothing()) {
   6616    return;
   6617  }
   6618 
   6619  nsCSSRendering::DrawTableBorderSegment(
   6620      aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect,
   6621      aIter.mTable->PresContext()->AppUnitsPerDevPixel(),
   6622      param->mStartBevelSide, param->mStartBevelOffset, param->mEndBevelSide,
   6623      param->mEndBevelOffset);
   6624 }
   6625 
   6626 // Pushes a border bevel triangle and substracts the relevant rectangle from
   6627 // aRect, which, after all the bevels, will end up being a solid segment rect.
   6628 static void AdjustAndPushBevel(wr::DisplayListBuilder& aBuilder,
   6629                               wr::LayoutRect& aRect, nscolor aColor,
   6630                               const nsCSSRendering::Bevel& aBevel,
   6631                               int32_t aAppUnitsPerDevPixel,
   6632                               bool aBackfaceIsVisible, bool aIsStart) {
   6633  if (!aBevel.mOffset) {
   6634    return;
   6635  }
   6636 
   6637  const auto kTransparent = wr::ToColorF(gfx::DeviceColor(0., 0., 0., 0.));
   6638  const bool horizontal =
   6639      aBevel.mSide == eSideTop || aBevel.mSide == eSideBottom;
   6640 
   6641  // Crappy CSS triangle as known by every web developer ever :)
   6642  Float offset = NSAppUnitsToFloatPixels(aBevel.mOffset, aAppUnitsPerDevPixel);
   6643  wr::LayoutRect bevelRect = aRect;
   6644  wr::BorderSide bevelBorder[4];
   6645  for (const auto i : mozilla::AllPhysicalSides()) {
   6646    bevelBorder[i] =
   6647        wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid);
   6648  }
   6649 
   6650  // We're creating a half-transparent triangle using the border primitive.
   6651  //
   6652  // Classic web-dev trick, with a gotcha: we use a single corner to avoid
   6653  // seams and rounding errors.
   6654  //
   6655  // Classic web-dev trick :P
   6656  auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0);
   6657  bevelBorder[aBevel.mSide].color = kTransparent;
   6658  if (aIsStart) {
   6659    if (horizontal) {
   6660      bevelBorder[eSideLeft].color = kTransparent;
   6661      borderWidths.left = offset;
   6662    } else {
   6663      bevelBorder[eSideTop].color = kTransparent;
   6664      borderWidths.top = offset;
   6665    }
   6666  } else {
   6667    if (horizontal) {
   6668      bevelBorder[eSideRight].color = kTransparent;
   6669      borderWidths.right = offset;
   6670    } else {
   6671      bevelBorder[eSideBottom].color = kTransparent;
   6672      borderWidths.bottom = offset;
   6673    }
   6674  }
   6675 
   6676  if (horizontal) {
   6677    if (aIsStart) {
   6678      aRect.min.x += offset;
   6679      aRect.max.x += offset;
   6680    } else {
   6681      bevelRect.min.x += aRect.width() - offset;
   6682      bevelRect.max.x += aRect.width() - offset;
   6683    }
   6684    aRect.max.x -= offset;
   6685    bevelRect.max.y = bevelRect.min.y + aRect.height();
   6686    bevelRect.max.x = bevelRect.min.x + offset;
   6687    if (aBevel.mSide == eSideTop) {
   6688      borderWidths.bottom = aRect.height();
   6689    } else {
   6690      borderWidths.top = aRect.height();
   6691    }
   6692  } else {
   6693    if (aIsStart) {
   6694      aRect.min.y += offset;
   6695      aRect.max.y += offset;
   6696    } else {
   6697      bevelRect.min.y += aRect.height() - offset;
   6698      bevelRect.max.y += aRect.height() - offset;
   6699    }
   6700    aRect.max.y -= offset;
   6701    bevelRect.max.x = bevelRect.min.x + aRect.width();
   6702    bevelRect.max.y = bevelRect.min.y + offset;
   6703    if (aBevel.mSide == eSideLeft) {
   6704      borderWidths.right = aRect.width();
   6705    } else {
   6706      borderWidths.left = aRect.width();
   6707    }
   6708  }
   6709 
   6710  Range<const wr::BorderSide> wrsides(bevelBorder, 4);
   6711  // It's important to _not_ anti-alias the bevel, because otherwise we wouldn't
   6712  // be able bevel to sides of the same color without bleeding in the middle.
   6713  aBuilder.PushBorder(bevelRect, bevelRect, aBackfaceIsVisible, borderWidths,
   6714                      wrsides, wr::EmptyBorderRadius(),
   6715                      wr::AntialiasBorder::No);
   6716 }
   6717 
   6718 static void CreateWRCommandsForBeveledBorder(
   6719    const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder,
   6720    const layers::StackingContextHelper& aSc, const nsPoint& aOffset,
   6721    nscoord aAppUnitsPerDevPixel) {
   6722  MOZ_ASSERT(aBorderParams.NeedToBevel());
   6723 
   6724  AutoTArray<nsCSSRendering::SolidBeveledBorderSegment, 3> segments;
   6725  nsCSSRendering::GetTableBorderSolidSegments(
   6726      segments, aBorderParams.mBorderStyle, aBorderParams.mBorderColor,
   6727      aBorderParams.mBorderRect, aAppUnitsPerDevPixel,
   6728      aBorderParams.mStartBevelSide, aBorderParams.mStartBevelOffset,
   6729      aBorderParams.mEndBevelSide, aBorderParams.mEndBevelOffset);
   6730 
   6731  for (const auto& segment : segments) {
   6732    auto rect = LayoutDeviceRect::FromUnknownRect(
   6733        NSRectToRect(segment.mRect + aOffset, aAppUnitsPerDevPixel));
   6734    auto r = wr::ToLayoutRect(rect);
   6735    auto color = wr::ToColorF(ToDeviceColor(segment.mColor));
   6736 
   6737    // Adjust for the start bevel if needed.
   6738    AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mStartBevel,
   6739                       aAppUnitsPerDevPixel, aBorderParams.mBackfaceIsVisible,
   6740                       true);
   6741 
   6742    AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mEndBevel,
   6743                       aAppUnitsPerDevPixel, aBorderParams.mBackfaceIsVisible,
   6744                       false);
   6745 
   6746    aBuilder.PushRect(r, r, aBorderParams.mBackfaceIsVisible, false, false,
   6747                      color);
   6748  }
   6749 }
   6750 
   6751 static void CreateWRCommandsForBorderSegment(
   6752    const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder,
   6753    const layers::StackingContextHelper& aSc, const nsPoint& aOffset,
   6754    nscoord aAppUnitsPerDevPixel) {
   6755  if (aBorderParams.NeedToBevel()) {
   6756    CreateWRCommandsForBeveledBorder(aBorderParams, aBuilder, aSc, aOffset,
   6757                                     aAppUnitsPerDevPixel);
   6758    return;
   6759  }
   6760 
   6761  auto borderRect = LayoutDeviceRect::FromUnknownRect(
   6762      NSRectToRect(aBorderParams.mBorderRect + aOffset, aAppUnitsPerDevPixel));
   6763 
   6764  wr::LayoutRect r = wr::ToLayoutRect(borderRect);
   6765  wr::BorderSide wrSide[4];
   6766  for (const auto i : mozilla::AllPhysicalSides()) {
   6767    wrSide[i] = wr::ToBorderSide(ToDeviceColor(aBorderParams.mBorderColor),
   6768                                 StyleBorderStyle::None);
   6769  }
   6770  const bool horizontal = aBorderParams.mStartBevelSide == eSideTop ||
   6771                          aBorderParams.mStartBevelSide == eSideBottom;
   6772  auto borderWidth = horizontal ? r.height() : r.width();
   6773 
   6774  // All border style is set to none except left side. So setting the widths of
   6775  // each side to width of rect is fine.
   6776  auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0);
   6777 
   6778  wrSide[horizontal ? eSideTop : eSideLeft] = wr::ToBorderSide(
   6779      ToDeviceColor(aBorderParams.mBorderColor), aBorderParams.mBorderStyle);
   6780 
   6781  if (horizontal) {
   6782    borderWidths.top = borderWidth;
   6783  } else {
   6784    borderWidths.left = borderWidth;
   6785  }
   6786 
   6787  Range<const wr::BorderSide> wrsides(wrSide, 4);
   6788  aBuilder.PushBorder(r, r, aBorderParams.mBackfaceIsVisible, borderWidths,
   6789                      wrsides, wr::EmptyBorderRadius());
   6790 }
   6791 
   6792 void BCBlockDirSeg::CreateWebRenderCommands(
   6793    BCPaintBorderIterator& aIter, nscoord aInlineSegBSize,
   6794    wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc,
   6795    const nsPoint& aOffset) {
   6796  Maybe<BCBorderParameters> param =
   6797      BuildBorderParameters(aIter, aInlineSegBSize);
   6798  if (param.isNothing()) {
   6799    return;
   6800  }
   6801 
   6802  CreateWRCommandsForBorderSegment(
   6803      *param, aBuilder, aSc, aOffset,
   6804      aIter.mTable->PresContext()->AppUnitsPerDevPixel());
   6805 }
   6806 
   6807 /**
   6808 * Advance the start point of a segment
   6809 */
   6810 void BCBlockDirSeg::AdvanceOffsetB() { mOffsetB += mLength - mBEndOffset; }
   6811 
   6812 /**
   6813 * Accumulate the current segment
   6814 */
   6815 void BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) {
   6816  mLastCell = aIter.mCell;
   6817  mLength += aIter.mRow->BSize(aIter.mTableWM);
   6818 }
   6819 
   6820 BCInlineDirSeg::BCInlineDirSeg()
   6821    : mIsIEndBevel(false),
   6822      mIEndBevelOffset(0),
   6823      mIEndBevelSide(LogicalSide::BStart),
   6824      mEndOffset(0),
   6825      mOwner(eTableOwner) {
   6826  mOffsetI = mOffsetB = mLength = mWidth = mIStartBevelOffset = 0;
   6827  mIStartBevelSide = LogicalSide::BStart;
   6828  mFirstCell = mAjaCell = nullptr;
   6829 }
   6830 
   6831 /** Initialize an inline-dir border segment for painting
   6832  * @param aIter              - iterator storing the current and adjacent frames
   6833  * @param aBorderOwner       - which frame owns the border
   6834  * @param aBEndBlockSegISize - block-dir segment width coming from up
   6835  * @param aInlineSegBSize    - the thickness of the segment
   6836  +  */
   6837 void BCInlineDirSeg::Start(BCPaintBorderIterator& aIter,
   6838                           BCBorderOwner aBorderOwner,
   6839                           nscoord aBEndBlockSegISize,
   6840                           nscoord aInlineSegBSize) {
   6841  LogicalSide cornerOwnerSide = LogicalSide::BStart;
   6842  bool bevel = false;
   6843 
   6844  mOwner = aBorderOwner;
   6845  nscoord cornerSubWidth =
   6846      (aIter.mBCData) ? aIter.mBCData->GetCorner(cornerOwnerSide, bevel) : 0;
   6847 
   6848  bool iStartBevel = (aInlineSegBSize > 0) ? bevel : false;
   6849  int32_t relColIndex = aIter.GetRelativeColIndex();
   6850  nscoord maxBlockSegISize =
   6851      std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aBEndBlockSegISize);
   6852  nscoord offset = CalcHorCornerOffset(cornerOwnerSide, cornerSubWidth,
   6853                                       maxBlockSegISize, true, iStartBevel);
   6854  mIStartBevelOffset =
   6855      (iStartBevel && (aInlineSegBSize > 0)) ? maxBlockSegISize : 0;
   6856  // XXX this assumes that only corners where 2 segments join can be beveled
   6857  mIStartBevelSide =
   6858      (aBEndBlockSegISize > 0) ? LogicalSide::BEnd : LogicalSide::BStart;
   6859  mOffsetI += offset;
   6860  mLength = -offset;
   6861  mWidth = aInlineSegBSize;
   6862  mFirstCell = aIter.mCell;
   6863  mAjaCell = (aIter.IsDamageAreaBStartMost())
   6864                 ? nullptr
   6865                 : aIter.mBlockDirInfo[relColIndex].mLastCell;
   6866 }
   6867 
   6868 /**
   6869 * Compute the offsets for the iEnd corner of an inline-dir segment
   6870 * @param aIter         - iterator containing the structural information
   6871 * @param aIStartSegISize - the iSize of the block-dir segment joining the
   6872 * corner at the start
   6873 */
   6874 void BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator& aIter,
   6875                                   nscoord aIStartSegISize) {
   6876  LogicalSide ownerSide = LogicalSide::BStart;
   6877  nscoord cornerSubWidth = 0;
   6878  bool bevel = false;
   6879  if (aIter.mBCData) {
   6880    cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
   6881  }
   6882 
   6883  mIsIEndBevel = (mWidth > 0) ? bevel : 0;
   6884  int32_t relColIndex = aIter.GetRelativeColIndex();
   6885  nscoord verWidth =
   6886      std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aIStartSegISize);
   6887  mEndOffset = CalcHorCornerOffset(ownerSide, cornerSubWidth, verWidth, false,
   6888                                   mIsIEndBevel);
   6889  mLength += mEndOffset;
   6890  mIEndBevelOffset = mIsIEndBevel ? verWidth : 0;
   6891  mIEndBevelSide =
   6892      (aIStartSegISize > 0) ? LogicalSide::BEnd : LogicalSide::BStart;
   6893 }
   6894 
   6895 Maybe<BCBorderParameters> BCInlineDirSeg::BuildBorderParameters(
   6896    BCPaintBorderIterator& aIter) {
   6897  BCBorderParameters result;
   6898 
   6899  // get the border style, color and paint the segment
   6900  LogicalSide side =
   6901      aIter.IsDamageAreaBEndMost() ? LogicalSide::BEnd : LogicalSide::BStart;
   6902  nsIFrame* rg = aIter.mRg;
   6903  if (!rg) ABORT1(Nothing());
   6904  nsIFrame* row = aIter.mRow;
   6905  if (!row) ABORT1(Nothing());
   6906  nsIFrame* cell = mFirstCell;
   6907  nsIFrame* col;
   6908  nsIFrame* owner = nullptr;
   6909  result.mBackfaceIsVisible = true;
   6910  result.mBorderStyle = StyleBorderStyle::Solid;
   6911  result.mBorderColor = 0xFFFFFFFF;
   6912 
   6913  switch (mOwner) {
   6914    case eTableOwner:
   6915      owner = aIter.mTable;
   6916      break;
   6917    case eAjaColGroupOwner:
   6918      NS_ERROR("neighboring colgroups can never own an inline-dir border");
   6919      [[fallthrough]];
   6920    case eColGroupOwner:
   6921      NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
   6922                   "col group can own border only at the table edge");
   6923      col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
   6924      if (!col) ABORT1(Nothing());
   6925      owner = col->GetParent();
   6926      break;
   6927    case eAjaColOwner:
   6928      NS_ERROR("neighboring column can never own an inline-dir border");
   6929      [[fallthrough]];
   6930    case eColOwner:
   6931      NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
   6932                   "col can own border only at the table edge");
   6933      owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
   6934      break;
   6935    case eAjaRowGroupOwner:
   6936      side = LogicalSide::BEnd;
   6937      rg = (aIter.IsTableBEndMost()) ? aIter.mRg : aIter.mPrevRg;
   6938      [[fallthrough]];
   6939    case eRowGroupOwner:
   6940      owner = rg;
   6941      break;
   6942    case eAjaRowOwner:
   6943      side = LogicalSide::BEnd;
   6944      row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow;
   6945      [[fallthrough]];
   6946    case eRowOwner:
   6947      owner = row;
   6948      break;
   6949    case eAjaCellOwner:
   6950      side = LogicalSide::BEnd;
   6951      // if this is null due to the damage area origin-y > 0, then the border
   6952      // won't show up anyway
   6953      cell = mAjaCell;
   6954      [[fallthrough]];
   6955    case eCellOwner:
   6956      owner = cell;
   6957      break;
   6958  }
   6959  if (owner) {
   6960    ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle,
   6961                        &result.mBorderColor);
   6962    result.mBackfaceIsVisible = !owner->BackfaceIsHidden();
   6963  }
   6964  nscoord smallHalf, largeHalf;
   6965  DivideBCBorderSize(mWidth, smallHalf, largeHalf);
   6966  LogicalRect segRect(aIter.mTableWM, mOffsetI, mOffsetB - largeHalf, mLength,
   6967                      mWidth);
   6968 
   6969  // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
   6970  result.mBorderRect =
   6971      segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize());
   6972  result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide);
   6973  result.mEndBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide);
   6974  result.mStartBevelOffset = mIStartBevelOffset;
   6975  result.mEndBevelOffset = mIEndBevelOffset;
   6976  // With inline-RTL directionality, the 'start' and 'end' of the inline-dir
   6977  // border segment need to be swapped because DrawTableBorderSegment will
   6978  // apply the 'start' bevel physically at the left or top edge, and 'end' at
   6979  // the right or bottom.
   6980  // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal
   6981  // writing mode, or "left" or "right" in vertical mode.
   6982  // DrawTableBorderSegment works purely with physical coordinates, so it
   6983  // expects startBevelOffset to be the indentation-from-the-left or top end
   6984  // of the border-segment, and endBevelOffset is the indentation-from-the-
   6985  // right or bottom end. If the writing mode is inline-RTL, our "start" and
   6986  // "end" will be reversed from this physical-coord view, so we have to swap
   6987  // them here.
   6988  if (aIter.mTableWM.IsBidiRTL()) {
   6989    std::swap(result.mStartBevelSide, result.mEndBevelSide);
   6990    std::swap(result.mStartBevelOffset, result.mEndBevelOffset);
   6991  }
   6992 
   6993  return Some(result);
   6994 }
   6995 
   6996 /**
   6997 * Paint the inline-dir segment
   6998 * @param aIter       - iterator containing the structural information
   6999 * @param aDrawTarget - the draw target
   7000 */
   7001 void BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter,
   7002                           DrawTarget& aDrawTarget) {
   7003  Maybe<BCBorderParameters> param = BuildBorderParameters(aIter);
   7004  if (param.isNothing()) {
   7005    return;
   7006  }
   7007 
   7008  nsCSSRendering::DrawTableBorderSegment(
   7009      aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect,
   7010      aIter.mTable->PresContext()->AppUnitsPerDevPixel(),
   7011      param->mStartBevelSide, param->mStartBevelOffset, param->mEndBevelSide,
   7012      param->mEndBevelOffset);
   7013 }
   7014 
   7015 void BCInlineDirSeg::CreateWebRenderCommands(
   7016    BCPaintBorderIterator& aIter, wr::DisplayListBuilder& aBuilder,
   7017    const layers::StackingContextHelper& aSc, const nsPoint& aPt) {
   7018  Maybe<BCBorderParameters> param = BuildBorderParameters(aIter);
   7019  if (param.isNothing()) {
   7020    return;
   7021  }
   7022 
   7023  CreateWRCommandsForBorderSegment(
   7024      *param, aBuilder, aSc, aPt,
   7025      aIter.mTable->PresContext()->AppUnitsPerDevPixel());
   7026 }
   7027 
   7028 /**
   7029 * Advance the start point of a segment
   7030 */
   7031 void BCInlineDirSeg::AdvanceOffsetI() { mOffsetI += (mLength - mEndOffset); }
   7032 
   7033 /**
   7034 * Accumulate the current segment
   7035 */
   7036 void BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) {
   7037  mLength += aIter.mBlockDirInfo[aIter.GetRelativeColIndex()].mColWidth;
   7038 }
   7039 
   7040 /**
   7041 * store the column width information while painting inline-dir segment
   7042 */
   7043 void BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) {
   7044  if (IsTableIEndMost()) {
   7045    mBlockDirInfo[aIndex].mColWidth = mBlockDirInfo[aIndex - 1].mColWidth;
   7046  } else {
   7047    nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex);
   7048    if (!col) ABORT0();
   7049    mBlockDirInfo[aIndex].mColWidth = col->ISize(mTableWM);
   7050  }
   7051 }
   7052 /**
   7053 * Determine if a block-dir segment owns the corner
   7054 */
   7055 bool BCPaintBorderIterator::BlockDirSegmentOwnsCorner() {
   7056  LogicalSide cornerOwnerSide = LogicalSide::BStart;
   7057  bool bevel = false;
   7058  if (mBCData) {
   7059    mBCData->GetCorner(cornerOwnerSide, bevel);
   7060  }
   7061  // unitialized ownerside, bevel
   7062  return (LogicalSide::BStart == cornerOwnerSide) ||
   7063         (LogicalSide::BEnd == cornerOwnerSide);
   7064 }
   7065 
   7066 /**
   7067 * Paint if necessary an inline-dir segment, otherwise accumulate it
   7068 * @param aDrawTarget - the draw target
   7069 */
   7070 void BCPaintBorderIterator::AccumulateOrDoActionInlineDirSegment(
   7071    BCPaintBorderAction& aAction) {
   7072  int32_t relColIndex = GetRelativeColIndex();
   7073  // store the current col width if it hasn't been already
   7074  if (mBlockDirInfo[relColIndex].mColWidth < 0) {
   7075    StoreColumnWidth(relColIndex);
   7076  }
   7077 
   7078  BCBorderOwner borderOwner = eCellOwner;
   7079  BCBorderOwner ignoreBorderOwner;
   7080  bool isSegStart = true;
   7081  bool ignoreSegStart;
   7082 
   7083  nscoord iStartSegISize =
   7084      mBCData ? mBCData->GetIStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
   7085  nscoord bStartSegBSize =
   7086      mBCData ? mBCData->GetBStartEdge(borderOwner, isSegStart) : 0;
   7087 
   7088  if (mIsNewRow || (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) {
   7089    // reset for every new row and on the bottom of the last row
   7090    mInlineSeg.mOffsetB = mNextOffsetB;
   7091    mNextOffsetB = mNextOffsetB + mRow->BSize(mTableWM);
   7092    mInlineSeg.mOffsetI = mInitialOffsetI;
   7093    mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
   7094  }
   7095 
   7096  if (!IsDamageAreaIStartMost() &&
   7097      (isSegStart || IsDamageAreaIEndMost() || BlockDirSegmentOwnsCorner())) {
   7098    // paint the previous seg or the current one if IsDamageAreaIEndMost()
   7099    if (mInlineSeg.mLength > 0) {
   7100      mInlineSeg.GetIEndCorner(*this, iStartSegISize);
   7101      if (mInlineSeg.mWidth > 0) {
   7102        if (aAction.mMode == BCPaintBorderAction::Mode::Paint) {
   7103          mInlineSeg.Paint(*this, aAction.mPaintData.mDrawTarget);
   7104        } else {
   7105          MOZ_ASSERT(aAction.mMode ==
   7106                     BCPaintBorderAction::Mode::CreateWebRenderCommands);
   7107          mInlineSeg.CreateWebRenderCommands(
   7108              *this, aAction.mCreateWebRenderCommandsData.mBuilder,
   7109              aAction.mCreateWebRenderCommandsData.mSc,
   7110              aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame);
   7111        }
   7112      }
   7113      mInlineSeg.AdvanceOffsetI();
   7114    }
   7115    mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
   7116  }
   7117  mInlineSeg.IncludeCurrentBorder(*this);
   7118  mBlockDirInfo[relColIndex].mWidth = iStartSegISize;
   7119  mBlockDirInfo[relColIndex].mLastCell = mCell;
   7120 }
   7121 
   7122 /**
   7123 * Paint if necessary a block-dir segment, otherwise accumulate it
   7124 * @param aDrawTarget - the draw target
   7125 */
   7126 void BCPaintBorderIterator::AccumulateOrDoActionBlockDirSegment(
   7127    BCPaintBorderAction& aAction) {
   7128  BCBorderOwner borderOwner = eCellOwner;
   7129  BCBorderOwner ignoreBorderOwner;
   7130  bool isSegStart = true;
   7131  bool ignoreSegStart;
   7132 
   7133  nscoord blockSegISize =
   7134      mBCData ? mBCData->GetIStartEdge(borderOwner, isSegStart) : 0;
   7135  nscoord inlineSegBSize =
   7136      mBCData ? mBCData->GetBStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
   7137 
   7138  int32_t relColIndex = GetRelativeColIndex();
   7139  BCBlockDirSeg& blockDirSeg = mBlockDirInfo[relColIndex];
   7140  if (!blockDirSeg.mCol) {  // on the first damaged row and the first segment in
   7141                            // the col
   7142    blockDirSeg.Initialize(*this);
   7143    blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize,
   7144                      Nothing{});
   7145  }
   7146 
   7147  if (!IsDamageAreaBStartMost() &&
   7148      (isSegStart || IsDamageAreaBEndMost() || IsAfterRepeatedHeader() ||
   7149       StartRepeatedFooter())) {
   7150    Maybe<nscoord> emptyRowEndSize;
   7151    // paint the previous seg or the current one if IsDamageAreaBEndMost()
   7152    if (blockDirSeg.mLength > 0) {
   7153      blockDirSeg.GetBEndCorner(*this, inlineSegBSize);
   7154      if (blockDirSeg.mWidth > 0) {
   7155        if (aAction.mMode == BCPaintBorderAction::Mode::Paint) {
   7156          blockDirSeg.Paint(*this, aAction.mPaintData.mDrawTarget,
   7157                            inlineSegBSize);
   7158        } else {
   7159          MOZ_ASSERT(aAction.mMode ==
   7160                     BCPaintBorderAction::Mode::CreateWebRenderCommands);
   7161          blockDirSeg.CreateWebRenderCommands(
   7162              *this, inlineSegBSize,
   7163              aAction.mCreateWebRenderCommandsData.mBuilder,
   7164              aAction.mCreateWebRenderCommandsData.mSc,
   7165              aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame);
   7166        }
   7167      }
   7168      blockDirSeg.AdvanceOffsetB();
   7169      if (mRow->PrincipalChildList().IsEmpty()) {
   7170        emptyRowEndSize = Some(mRow->BSize(mTableWM));
   7171      }
   7172    }
   7173    blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize,
   7174                      emptyRowEndSize);
   7175  }
   7176  blockDirSeg.IncludeCurrentBorder(*this);
   7177  mPrevInlineSegBSize = inlineSegBSize;
   7178 }
   7179 
   7180 /**
   7181 * Reset the block-dir information cache
   7182 */
   7183 void BCPaintBorderIterator::ResetVerInfo() {
   7184  if (mBlockDirInfo) {
   7185    memset(mBlockDirInfo.get(), 0,
   7186           mDamageArea.ColCount() * sizeof(BCBlockDirSeg));
   7187    // XXX reinitialize properly
   7188    for (auto xIndex : IntegerRange(mDamageArea.ColCount())) {
   7189      mBlockDirInfo[xIndex].mColWidth = -1;
   7190    }
   7191  }
   7192 }
   7193 
   7194 void nsTableFrame::IterateBCBorders(BCPaintBorderAction& aAction,
   7195                                    const nsRect& aDirtyRect) {
   7196  // We first transfer the aDirtyRect into cellmap coordinates to compute which
   7197  // cell borders need to be painted
   7198  BCPaintBorderIterator iter(this);
   7199  if (!iter.SetDamageArea(aDirtyRect)) {
   7200    return;
   7201  }
   7202 
   7203  // XXX comment still has physical terminology
   7204  // First, paint all of the vertical borders from top to bottom and left to
   7205  // right as they become complete. They are painted first, since they are less
   7206  // efficient to paint than horizontal segments. They were stored with as few
   7207  // segments as possible (since horizontal borders are painted last and
   7208  // possibly over them). For every cell in a row that fails in the damage are
   7209  // we look up if the current border would start a new segment, if so we paint
   7210  // the previously stored vertical segment and start a new segment. After
   7211  // this we  the now active segment with the current border. These
   7212  // segments are stored in mBlockDirInfo to be used on the next row
   7213  for (iter.First(); !iter.mAtEnd; iter.Next()) {
   7214    iter.AccumulateOrDoActionBlockDirSegment(aAction);
   7215  }
   7216 
   7217  // Next, paint all of the inline-dir border segments from bStart to bEnd reuse
   7218  // the mBlockDirInfo array to keep track of col widths and block-dir segments
   7219  // for corner calculations
   7220  iter.Reset();
   7221  for (iter.First(); !iter.mAtEnd; iter.Next()) {
   7222    iter.AccumulateOrDoActionInlineDirSegment(aAction);
   7223  }
   7224 }
   7225 
   7226 /**
   7227 * Method to paint BCBorders, this does not use currently display lists although
   7228 * it will do this in future
   7229 * @param aDrawTarget - the rendering context
   7230 * @param aDirtyRect  - inside this rectangle the BC Borders will redrawn
   7231 */
   7232 void nsTableFrame::PaintBCBorders(DrawTarget& aDrawTarget,
   7233                                  const nsRect& aDirtyRect) {
   7234  BCPaintBorderAction action(aDrawTarget);
   7235  IterateBCBorders(action, aDirtyRect);
   7236 }
   7237 
   7238 void nsTableFrame::CreateWebRenderCommandsForBCBorders(
   7239    wr::DisplayListBuilder& aBuilder,
   7240    const mozilla::layers::StackingContextHelper& aSc,
   7241    const nsRect& aVisibleRect, const nsPoint& aOffsetToReferenceFrame) {
   7242  BCPaintBorderAction action(aBuilder, aSc, aOffsetToReferenceFrame);
   7243  // We always draw whole table border for webrender. Passing the visible rect
   7244  // dirty rect.
   7245  IterateBCBorders(action, aVisibleRect - aOffsetToReferenceFrame);
   7246 }
   7247 
   7248 bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) {
   7249  bool result = false;
   7250  nsTableCellMap* cellMap = GetCellMap();
   7251  MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated.");
   7252  if (cellMap) {
   7253    result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols);
   7254  }
   7255  return result;
   7256 }
   7257 
   7258 bool nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) {
   7259  bool result = false;
   7260  nsTableCellMap* cellMap = GetCellMap();
   7261  MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated.");
   7262  if (cellMap) {
   7263    result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols);
   7264  }
   7265  return result;
   7266 }
   7267 
   7268 /* static */
   7269 void nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame,
   7270                                        const nsRect& aOrigRect,
   7271                                        const nsRect& aOrigInkOverflow,
   7272                                        bool aIsFirstReflow) {
   7273  nsIFrame* parent = aFrame->GetParent();
   7274  NS_ASSERTION(parent, "What happened here?");
   7275 
   7276  if (parent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
   7277    // Don't bother; we'll invalidate the parent's overflow rect when
   7278    // we finish reflowing it.
   7279    return;
   7280  }
   7281 
   7282  // The part that looks at both the rect and the overflow rect is a
   7283  // bit of a hack.  See nsBlockFrame::ReflowLine for an eloquent
   7284  // description of its hackishness.
   7285  //
   7286  // This doesn't really make sense now that we have DLBI.
   7287  // This code can probably be simplified a fair bit.
   7288  nsRect inkOverflow = aFrame->InkOverflowRect();
   7289  if (aIsFirstReflow || aOrigRect.TopLeft() != aFrame->GetPosition() ||
   7290      aOrigInkOverflow.TopLeft() != inkOverflow.TopLeft()) {
   7291    // Invalidate the old and new overflow rects.  Note that if the
   7292    // frame moved, we can't just use aOrigInkOverflow, since it's in
   7293    // coordinates relative to the old position.  So invalidate via
   7294    // aFrame's parent, and reposition that overflow rect to the right
   7295    // place.
   7296    // XXXbz this doesn't handle outlines, does it?
   7297    aFrame->InvalidateFrame();
   7298    parent->InvalidateFrameWithRect(aOrigInkOverflow + aOrigRect.TopLeft());
   7299  } else if (aOrigRect.Size() != aFrame->GetSize() ||
   7300             aOrigInkOverflow.Size() != inkOverflow.Size()) {
   7301    aFrame->InvalidateFrameWithRect(aOrigInkOverflow);
   7302    aFrame->InvalidateFrame();
   7303  }
   7304 }
   7305 
   7306 void nsTableFrame::AppendDirectlyOwnedAnonBoxes(
   7307    nsTArray<OwnedAnonBox>& aResult) {
   7308  nsIFrame* wrapper = GetParent();
   7309  MOZ_ASSERT(wrapper->Style()->GetPseudoType() == PseudoStyleType::tableWrapper,
   7310             "What happened to our parent?");
   7311  aResult.AppendElement(
   7312      OwnedAnonBox(wrapper, &UpdateStyleOfOwnedAnonBoxesForTableWrapper));
   7313 }
   7314 
   7315 /* static */
   7316 void nsTableFrame::UpdateStyleOfOwnedAnonBoxesForTableWrapper(
   7317    nsIFrame* aOwningFrame, nsIFrame* aWrapperFrame,
   7318    ServoRestyleState& aRestyleState) {
   7319  MOZ_ASSERT(
   7320      aWrapperFrame->Style()->GetPseudoType() == PseudoStyleType::tableWrapper,
   7321      "What happened to our parent?");
   7322 
   7323  RefPtr<ComputedStyle> newStyle =
   7324      aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
   7325          PseudoStyleType::tableWrapper, aOwningFrame->Style());
   7326 
   7327  // Figure out whether we have an actual change.  It's important that we do
   7328  // this, even though all the wrapper's changes are due to properties it
   7329  // inherits from us, because it's possible that no one ever asked us for those
   7330  // style structs and hence changes to them aren't reflected in
   7331  // the handled changes at all.
   7332  //
   7333  // Also note that extensions can add/remove stylesheets that change the styles
   7334  // of anonymous boxes directly, so we need to handle that potential change
   7335  // here.
   7336  //
   7337  // NOTE(emilio): We can't use the ChangesHandledFor optimization (and we
   7338  // assert against that), because the table wrapper is up in the frame tree
   7339  // compared to the owner frame.
   7340  uint32_t equalStructs;  // Not used, actually.
   7341  nsChangeHint wrapperHint =
   7342      aWrapperFrame->Style()->CalcStyleDifference(*newStyle, &equalStructs);
   7343 
   7344  if (wrapperHint) {
   7345    aRestyleState.ChangeList().AppendChange(
   7346        aWrapperFrame, aWrapperFrame->GetContent(), wrapperHint);
   7347  }
   7348 
   7349  for (nsIFrame* cur = aWrapperFrame; cur; cur = cur->GetNextContinuation()) {
   7350    cur->SetComputedStyle(newStyle);
   7351  }
   7352 
   7353  MOZ_ASSERT(!aWrapperFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES),
   7354             "Wrapper frame doesn't have any anon boxes of its own!");
   7355 }
   7356 
   7357 namespace mozilla {
   7358 
   7359 nsRect nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder,
   7360                                     bool* aSnap) const {
   7361  *aSnap = false;
   7362  return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
   7363 }
   7364 
   7365 nsDisplayTableBackgroundSet::nsDisplayTableBackgroundSet(
   7366    nsDisplayListBuilder* aBuilder, nsIFrame* aTable)
   7367    : mBuilder(aBuilder),
   7368      mColGroupBackgrounds(aBuilder),
   7369      mColBackgrounds(aBuilder) {
   7370  mPrevTableBackgroundSet = mBuilder->SetTableBackgroundSet(this);
   7371  mozilla::DebugOnly<const nsIFrame*> reference =
   7372      mBuilder->FindReferenceFrameFor(aTable, &mToReferenceFrame);
   7373  MOZ_ASSERT(nsLayoutUtils::FindNearestCommonAncestorFrame(reference, aTable));
   7374  mDirtyRect = mBuilder->GetDirtyRect();
   7375  mCombinedTableClipChain =
   7376      mBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
   7377  mTableASR = mBuilder->CurrentActiveScrolledRoot();
   7378 }
   7379 
   7380 // A display item that draws all collapsed borders for a table.
   7381 // At some point, we may want to find a nicer partitioning for dividing
   7382 // border-collapse segments into their own display items.
   7383 class nsDisplayTableBorderCollapse final : public nsDisplayTableItem {
   7384 public:
   7385  nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder,
   7386                               nsTableFrame* aFrame)
   7387      : nsDisplayTableItem(aBuilder, aFrame) {
   7388    MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse);
   7389  }
   7390 
   7391  MOZ_COUNTED_DTOR_FINAL(nsDisplayTableBorderCollapse)
   7392 
   7393  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
   7394  bool CreateWebRenderCommands(
   7395      wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
   7396      const StackingContextHelper& aSc,
   7397      layers::RenderRootStateManager* aManager,
   7398      nsDisplayListBuilder* aDisplayListBuilder) override;
   7399  NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE)
   7400 };
   7401 
   7402 void nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder,
   7403                                         gfxContext* aCtx) {
   7404  nsPoint pt = ToReferenceFrame();
   7405  DrawTarget* drawTarget = aCtx->GetDrawTarget();
   7406 
   7407  gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
   7408      pt, mFrame->PresContext()->AppUnitsPerDevPixel());
   7409 
   7410  // XXX we should probably get rid of this translation at some stage
   7411  // But that would mean modifying PaintBCBorders, ugh
   7412  AutoRestoreTransform autoRestoreTransform(drawTarget);
   7413  drawTarget->SetTransform(
   7414      drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
   7415 
   7416  static_cast<nsTableFrame*>(mFrame)->PaintBCBorders(
   7417      *drawTarget, GetPaintRect(aBuilder, aCtx) - pt);
   7418 }
   7419 
   7420 bool nsDisplayTableBorderCollapse::CreateWebRenderCommands(
   7421    wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
   7422    const StackingContextHelper& aSc,
   7423    mozilla::layers::RenderRootStateManager* aManager,
   7424    nsDisplayListBuilder* aDisplayListBuilder) {
   7425  bool dummy;
   7426  static_cast<nsTableFrame*>(mFrame)->CreateWebRenderCommandsForBCBorders(
   7427      aBuilder, aSc, GetBounds(aDisplayListBuilder, &dummy),
   7428      ToReferenceFrame());
   7429  return true;
   7430 }
   7431 
   7432 }  // namespace mozilla