Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/tables/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 281 kB image not shown  

Quelle  nsTableFrame.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "nsTableFrame.h"

#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/Likely.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/WritingModes.h"

#include "gfxContext.h"
#include "nsCOMPtr.h"
#include "mozilla/ComputedStyle.h"
#include "nsIFrameInlines.h"
#include "nsFrameList.h"
#include "nsStyleConsts.h"
#include "nsIContent.h"
#include "nsCellMap.h"
#include "nsTableCellFrame.h"
#include "nsHTMLParts.h"
#include "nsTableColFrame.h"
#include "nsTableColGroupFrame.h"
#include "nsTableRowFrame.h"
#include "nsTableRowGroupFrame.h"
#include "nsTableWrapperFrame.h"

#include "BasicTableLayoutStrategy.h"
#include "FixedTableLayoutStrategy.h"

#include "nsPresContext.h"
#include "nsContentUtils.h"
#include "nsCSSRendering.h"
#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"
#include "nsIScriptError.h"
#include "nsFrameManager.h"
#include "nsError.h"
#include "nsCSSFrameConstructor.h"
#include "mozilla/Range.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "nsDisplayList.h"
#include "nsCSSProps.h"
#include "nsLayoutUtils.h"
#include "nsStyleChangeList.h"
#include <algorithm>

#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/RenderRootStateManager.h"

using namespace mozilla;
using namespace mozilla::image;
using namespace mozilla::layout;

using mozilla::gfx::AutoRestoreTransform;
using mozilla::gfx::DrawTarget;
using mozilla::gfx::Float;
using mozilla::gfx::ToDeviceColor;

namespace mozilla {

struct TableReflowInput final {
  TableReflowInput(const ReflowInput& aReflowInput,
                   const LogicalMargin& aBorderPadding, TableReflowMode aMode)
      : mReflowInput(aReflowInput),
        mWM(aReflowInput.GetWritingMode()),
        mAvailSize(mWM) {
    MOZ_ASSERT(mReflowInput.mFrame->IsTableFrame(),
               "TableReflowInput should only be created for nsTableFrame");
    auto* table = static_cast<nsTableFrame*>(mReflowInput.mFrame);

    mICoord = aBorderPadding.IStart(mWM) + table->GetColSpacing(-1);
    mAvailSize.ISize(mWM) =
        std::max(0, mReflowInput.ComputedISize() - table->GetColSpacing(-1) -
                        table->GetColSpacing(table->GetColCount()));

    mAvailSize.BSize(mWM) = aMode == TableReflowMode::Measuring
                                ? NS_UNCONSTRAINEDSIZE
                                : mReflowInput.AvailableBSize();
    AdvanceBCoord(aBorderPadding.BStart(mWM) +
                  (!table->GetPrevInFlow() ? table->GetRowSpacing(-1) : 0));
    if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
        StyleBoxDecorationBreak::Clone) {
      // At this point, we're assuming we won't be the last fragment, so we only
      // reserve space for block-end border-padding if we're cloning it on each
      // fragment; and we don't need to reserve any row-spacing for this
      // hypothetical fragmentation, either.
      ReduceAvailableBSizeBy(aBorderPadding.BEnd(mWM));
    }
  }

  // Advance to the next block-offset and reduce the available block-size.
  void AdvanceBCoord(nscoord aAmount) {
    mBCoord += aAmount;
    ReduceAvailableBSizeBy(aAmount);
  }

  const LogicalSize& AvailableSize() const { return mAvailSize; }

  // The real reflow input of the table frame.
  const ReflowInput& mReflowInput;

  // Stationary inline-offset, which won't change after the constructor.
  nscoord mICoord = 0;

  // Running block-offset, which will be adjusted as we reflow children.
  nscoord mBCoord = 0;

 private:
  void ReduceAvailableBSizeBy(nscoord aAmount) {
    if (mAvailSize.BSize(mWM) == NS_UNCONSTRAINEDSIZE) {
      return;
    }
    mAvailSize.BSize(mWM) -= aAmount;
    mAvailSize.BSize(mWM) = std::max(0, mAvailSize.BSize(mWM));
  }

  // mReflowInput's (i.e. table frame's) writing-mode.
  WritingMode mWM;

  // The available size for children. The inline-size is stationary after the
  // constructor, but the block-size will be adjusted as we reflow children.
  LogicalSize mAvailSize;
};

struct TableBCData final {
  TableArea mDamageArea;
  nscoord mBStartBorderWidth = 0;
  nscoord mIEndBorderWidth = 0;
  nscoord mBEndBorderWidth = 0;
  nscoord mIStartBorderWidth = 0;
};

}  // namespace mozilla

/********************************************************************************
 ** nsTableFrame **
 ********************************************************************************/


ComputedStyle* nsTableFrame::GetParentComputedStyle(
    nsIFrame** aProviderFrame) const {
  // Since our parent, the table wrapper frame, returned this frame, we
  // must return whatever our parent would normally have returned.

  MOZ_ASSERT(GetParent(), "table constructed without table wrapper");
  if (!mContent->GetParent() && !Style()->IsPseudoOrAnonBox()) {
    // We're the root.  We have no ComputedStyle parent.
    *aProviderFrame = nullptr;
    return nullptr;
  }

  return GetParent()->DoGetParentComputedStyle(aProviderFrame);
}

nsTableFrame::nsTableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
                           ClassID aID)
    : nsContainerFrame(aStyle, aPresContext, aID) {
  memset(&mBits, 0, sizeof(mBits));
}

void nsTableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                        nsIFrame* aPrevInFlow) {
  MOZ_ASSERT(!mCellMap, "Init called twice");
  MOZ_ASSERT(!mTableLayoutStrategy, "Init called twice");
  MOZ_ASSERT(!aPrevInFlow || aPrevInFlow->IsTableFrame(),
             "prev-in-flow must be of same type");

  // Let the base class do its processing
  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);

  // see if border collapse is on, if so set it
  const nsStyleTableBorder* tableStyle = StyleTableBorder();
  bool borderCollapse =
      (StyleBorderCollapse::Collapse == tableStyle->mBorderCollapse);
  SetBorderCollapse(borderCollapse);
  if (borderCollapse) {
    SetNeedToCalcHasBCBorders(true);
  }

  if (!aPrevInFlow) {
    // If we're the first-in-flow, we manage the cell map & layout strategy that
    // get used by our continuation chain:
    mCellMap = MakeUnique<nsTableCellMap>(*this, borderCollapse);
    if (IsAutoLayout()) {
      mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this);
    } else {
      mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this);
    }
  } else {
    // Set my isize, because all frames in a table flow are the same isize and
    // code in nsTableWrapperFrame depends on this being set.
    WritingMode wm = GetWritingMode();
    SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm)));
  }
}

// Define here (Rather than in the header), even if it's trival, to avoid
// UniquePtr members causing compile errors when their destructors are
// implicitly inserted into this destructor. Destruction requires
// the full definition of types that these UniquePtrs are managing, and
// the header only has forward declarations of them.
nsTableFrame::~nsTableFrame() = default;

void nsTableFrame::Destroy(DestroyContext& aContext) {
  MOZ_ASSERT(!mBits.mIsDestroying);
  mBits.mIsDestroying = true;
  mColGroups.DestroyFrames(aContext);
  nsContainerFrame::Destroy(aContext);
}

// Make sure any views are positioned properly
void nsTableFrame::RePositionViews(nsIFrame* aFrame) {
  nsContainerFrame::PositionFrameView(aFrame);
  nsContainerFrame::PositionChildViews(aFrame);
}

static bool IsRepeatedFrame(nsIFrame* kidFrame) {
  return (kidFrame->IsTableRowFrame() || kidFrame->IsTableRowGroupFrame()) &&
         kidFrame->HasAnyStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
}

bool nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame,
                                  nsIFrame* aNextFrame) {
  const nsStyleDisplay* display = aSourceFrame->StyleDisplay();
  nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame);
  // don't allow a page break after a repeated element ...
  if ((display->BreakAfter() || (prevRg && prevRg->HasInternalBreakAfter())) &&
      !IsRepeatedFrame(aSourceFrame)) {
    return !(aNextFrame && IsRepeatedFrame(aNextFrame));  // or before
  }

  if (aNextFrame) {
    display = aNextFrame->StyleDisplay();
    // don't allow a page break before a repeated element ...
    nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame);
    if ((display->BreakBefore() ||
         (nextRg && nextRg->HasInternalBreakBefore())) &&
        !IsRepeatedFrame(aNextFrame)) {
      return !IsRepeatedFrame(aSourceFrame);  // or after
    }
  }
  return false;
}

/* static */
void nsTableFrame::PositionedTablePartMaybeChanged(nsIFrame* aFrame,
                                                   ComputedStyle* aOldStyle) {
  const bool wasPositioned =
      aOldStyle && aOldStyle->IsAbsPosContainingBlock(aFrame);
  const bool isPositioned = aFrame->IsAbsPosContainingBlock();
  MOZ_ASSERT(isPositioned == aFrame->Style()->IsAbsPosContainingBlock(aFrame));
  if (wasPositioned == isPositioned) {
    return;
  }

  nsTableFrame* tableFrame = GetTableFrame(aFrame);
  MOZ_ASSERT(tableFrame, "Should have a table frame here");
  tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());

  // Retrieve the positioned parts array for this table.
  FrameTArray* positionedParts =
      tableFrame->GetProperty(PositionedTablePartArray());

  // Lazily create the array if it doesn't exist yet.
  if (!positionedParts) {
    positionedParts = new FrameTArray;
    tableFrame->SetProperty(PositionedTablePartArray(), positionedParts);
  }

  if (isPositioned) {
    // Add this frame to the list.
    positionedParts->AppendElement(aFrame);
  } else {
    positionedParts->RemoveElement(aFrame);
  }
}

/* static */
void nsTableFrame::MaybeUnregisterPositionedTablePart(nsIFrame* aFrame) {
  if (!aFrame->IsAbsPosContainingBlock()) {
    return;
  }
  nsTableFrame* tableFrame = GetTableFrame(aFrame);
  tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());

  if (tableFrame->IsDestroying()) {
    return;  // We're throwing the table away anyways.
  }

  // Retrieve the positioned parts array for this table.
  FrameTArray* positionedParts =
      tableFrame->GetProperty(PositionedTablePartArray());

  // Remove the frame.
  MOZ_ASSERT(
      positionedParts && positionedParts->Contains(aFrame),
      "Asked to unregister a positioned table part that wasn't registered");
  if (positionedParts) {
    positionedParts->RemoveElement(aFrame);
  }
}

// XXX this needs to be cleaned up so that the frame constructor breaks out col
// group frames into a separate child list, bug 343048.
void nsTableFrame::SetInitialChildList(ChildListID aListID,
                                       nsFrameList&& aChildList) {
  if (aListID != FrameChildListID::Principal) {
    nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
    return;
  }

  MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(),
             "unexpected second call to SetInitialChildList");
#ifdef DEBUG
  for (nsIFrame* f : aChildList) {
    MOZ_ASSERT(f->GetParent() == this"Unexpected parent");
  }
#endif

  // XXXbz the below code is an icky cesspit that's only needed in its current
  // form for two reasons:
  // 1) Both rowgroups and column groups come in on the principal child list.
  while (aChildList.NotEmpty()) {
    nsIFrame* childFrame = aChildList.FirstChild();
    aChildList.RemoveFirstChild();
    const nsStyleDisplay* childDisplay = childFrame->StyleDisplay();

    if (mozilla::StyleDisplay::TableColumnGroup == childDisplay->mDisplay) {
      NS_ASSERTION(childFrame->IsTableColGroupFrame(),
                   "This is not a colgroup");
      mColGroups.AppendFrame(nullptr, childFrame);
    } else {  // row groups and unknown frames go on the main list for now
      mFrames.AppendFrame(nullptr, childFrame);
    }
  }

  // If we have a prev-in-flow, then we're a table that has been split and
  // so don't treat this like an append
  if (!GetPrevInFlow()) {
    // process col groups first so that real cols get constructed before
    // anonymous ones due to cells in rows.
    InsertColGroups(0, mColGroups);
    InsertRowGroups(mFrames);
    // calc collapsing borders
    if (IsBorderCollapse()) {
      SetFullBCDamageArea();
    }
  }
}

void nsTableFrame::RowOrColSpanChanged(nsTableCellFrame* aCellFrame) {
  if (aCellFrame) {
    nsTableCellMap* cellMap = GetCellMap();
    if (cellMap) {
      // for now just remove the cell from the map and reinsert it
      uint32_t rowIndex = aCellFrame->RowIndex();
      uint32_t colIndex = aCellFrame->ColIndex();
      RemoveCell(aCellFrame, rowIndex);
      AutoTArray<nsTableCellFrame*, 1> cells;
      cells.AppendElement(aCellFrame);
      InsertCells(cells, rowIndex, colIndex - 1);

      // XXX Should this use IntrinsicDirty::FrameAncestorsAndDescendants? It
      // currently doesn't need to, but it might given more optimization.
      PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
                                    NS_FRAME_IS_DIRTY);
    }
  }
}

/* ****** CellMap methods ******* */

/* return the effective col count */
int32_t nsTableFrame::GetEffectiveColCount() const {
  int32_t colCount = GetColCount();
  if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) {
    nsTableCellMap* cellMap = GetCellMap();
    if (!cellMap) {
      return 0;
    }
    // don't count cols at the end that don't have originating cells
    for (int32_t colIdx = colCount - 1; colIdx >= 0; colIdx--) {
      if (cellMap->GetNumCellsOriginatingInCol(colIdx) > 0) {
        break;
      }
      colCount--;
    }
  }
  return colCount;
}

int32_t nsTableFrame::GetIndexOfLastRealCol() {
  int32_t numCols = mColFrames.Length();
  if (numCols > 0) {
    for (int32_t colIdx = numCols - 1; colIdx >= 0; colIdx--) {
      nsTableColFrame* colFrame = GetColFrame(colIdx);
      if (colFrame) {
        if (eColAnonymousCell != colFrame->GetColType()) {
          return colIdx;
        }
      }
    }
  }
  return -1;
}

nsTableColFrame* nsTableFrame::GetColFrame(int32_t aColIndex) const {
  MOZ_ASSERT(!GetPrevInFlow(), "GetColFrame called on next in flow");
  int32_t numCols = mColFrames.Length();
  if ((aColIndex >= 0) && (aColIndex < numCols)) {
    MOZ_ASSERT(mColFrames.ElementAt(aColIndex));
    return mColFrames.ElementAt(aColIndex);
  } else {
    MOZ_ASSERT_UNREACHABLE("invalid col index");
    return nullptr;
  }
}

int32_t nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex,
                                          const nsTableCellFrame& aCell) const {
  nsTableCellMap* cellMap = GetCellMap();
  MOZ_ASSERT(nullptr != cellMap, "bad call, cellMap not yet allocated.");

  return cellMap->GetEffectiveRowSpan(aRowIndex, aCell.ColIndex());
}

int32_t nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell,
                                          nsCellMap* aCellMap) {
  nsTableCellMap* tableCellMap = GetCellMap();
  if (!tableCellMap) ABORT1(1);

  uint32_t colIndex = aCell.ColIndex();
  uint32_t rowIndex = aCell.RowIndex();

  if (aCellMap) {
    return aCellMap->GetRowSpan(rowIndex, colIndex, true);
  }
  return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex);
}

int32_t nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell,
                                          nsCellMap* aCellMap) const {
  nsTableCellMap* tableCellMap = GetCellMap();
  if (!tableCellMap) ABORT1(1);

  uint32_t colIndex = aCell.ColIndex();
  uint32_t rowIndex = aCell.RowIndex();

  if (aCellMap) {
    return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex);
  }
  return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex);
}

bool nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const {
  nsTableCellMap* tableCellMap = GetCellMap();
  if (!tableCellMap) ABORT1(1);
  return tableCellMap->HasMoreThanOneCell(aRowIndex);
}

void nsTableFrame::AdjustRowIndices(int32_t aRowIndex, int32_t aAdjustment) {
  // Iterate over the row groups and adjust the row indices of all rows
  // whose index is >= aRowIndex.
  RowGroupArray rowGroups = OrderedRowGroups();

  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
    rowGroups[rgIdx]->AdjustRowIndices(aRowIndex, aAdjustment);
  }
}

void nsTableFrame::ResetRowIndices(
    const nsFrameList::Slice& aRowGroupsToExclude) {
  // Iterate over the row groups and adjust the row indices of all rows
  // omit the rowgroups that will be inserted later
  mDeletedRowIndexRanges.clear();

  RowGroupArray rowGroups = OrderedRowGroups();

  nsTHashSet<nsTableRowGroupFrame*> excludeRowGroups;
  for (nsIFrame* excludeRowGroup : aRowGroupsToExclude) {
    excludeRowGroups.Insert(
        static_cast<nsTableRowGroupFrame*>(excludeRowGroup));
#ifdef DEBUG
    {
      // Check to make sure that the row indices of all rows in excluded row
      // groups are '0' (i.e. the initial value since they haven't been added
      // yet)
      const nsFrameList& rowFrames = excludeRowGroup->PrincipalChildList();
      for (nsIFrame* r : rowFrames) {
        auto* row = static_cast<nsTableRowFrame*>(r);
        MOZ_ASSERT(row->GetRowIndex() == 0,
                   "exclusions cannot be used for rows that were already added,"
                   "because we'd need to process mDeletedRowIndexRanges");
      }
    }
#endif
  }

  int32_t rowIndex = 0;
  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
    if (!excludeRowGroups.Contains(rgFrame)) {
      const nsFrameList& rowFrames = rgFrame->PrincipalChildList();
      for (nsIFrame* r : rowFrames) {
        if (mozilla::StyleDisplay::TableRow == r->StyleDisplay()->mDisplay) {
          auto* row = static_cast<nsTableRowFrame*>(r);
          row->SetRowIndex(rowIndex);
          rowIndex++;
        }
      }
    }
  }
}

void nsTableFrame::InsertColGroups(int32_t aStartColIndex,
                                   const nsFrameList::Slice& aColGroups) {
  int32_t colIndex = aStartColIndex;

  // XXX: We cannot use range-based for loop because AddColsToTable() can
  // destroy the nsTableColGroupFrame in the slice we're traversing! Need to
  // check the validity of *colGroupIter.
  auto colGroupIter = aColGroups.begin();
  for (auto colGroupIterEnd = aColGroups.end();
       *colGroupIter && colGroupIter != colGroupIterEnd; ++colGroupIter) {
    MOZ_ASSERT((*colGroupIter)->IsTableColGroupFrame());
    auto* cgFrame = static_cast<nsTableColGroupFrame*>(*colGroupIter);
    cgFrame->SetStartColumnIndex(colIndex);
    cgFrame->AddColsToTable(colIndex, false, cgFrame->PrincipalChildList());
    int32_t numCols = cgFrame->GetColCount();
    colIndex += numCols;
  }

  if (*colGroupIter) {
    nsTableColGroupFrame::ResetColIndices(*colGroupIter, colIndex);
  }
}

void nsTableFrame::InsertCol(nsTableColFrame& aColFrame, int32_t aColIndex) {
  mColFrames.InsertElementAt(aColIndex, &aColFrame);
  nsTableColType insertedColType = aColFrame.GetColType();
  int32_t numCacheCols = mColFrames.Length();
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    int32_t numMapCols = cellMap->GetColCount();
    if (numCacheCols > numMapCols) {
      bool removedFromCache = false;
      if (eColAnonymousCell != insertedColType) {
        nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1);
        if (lastCol) {
          nsTableColType lastColType = lastCol->GetColType();
          if (eColAnonymousCell == lastColType) {
            // remove the col from the cache
            mColFrames.RemoveLastElement();
            // remove the col from the synthetic col group
            nsTableColGroupFrame* lastColGroup =
                (nsTableColGroupFrame*)mColGroups.LastChild();
            if (lastColGroup) {
              MOZ_ASSERT(lastColGroup->IsSynthetic());
              DestroyContext context(PresShell());
              lastColGroup->RemoveChild(context, *lastCol, false);

              // remove the col group if it is empty
              if (lastColGroup->GetColCount() <= 0) {
                mColGroups.DestroyFrame(context, (nsIFrame*)lastColGroup);
              }
            }
            removedFromCache = true;
          }
        }
      }
      if (!removedFromCache) {
        cellMap->AddColsAtEnd(1);
      }
    }
  }
  // for now, just bail and recalc all of the collapsing borders
  if (IsBorderCollapse()) {
    TableArea damageArea(aColIndex, 0, GetColCount() - aColIndex,
                         GetRowCount());
    AddBCDamageArea(damageArea);
  }
}

void nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame,
                             int32_t aColIndex, bool aRemoveFromCache,
                             bool aRemoveFromCellMap) {
  if (aRemoveFromCache) {
    mColFrames.RemoveElementAt(aColIndex);
  }
  if (aRemoveFromCellMap) {
    nsTableCellMap* cellMap = GetCellMap();
    if (cellMap) {
      // If we have some anonymous cols at the end already, we just
      // add a new anonymous col.
      if (!mColFrames.IsEmpty() &&
          mColFrames.LastElement() &&  // XXXbz is this ever null?
          mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
        AppendAnonymousColFrames(1);
      } else {
        // All of our colframes correspond to actual <col> tags.  It's possible
        // that we still have at least as many <col> tags as we have logical
        // columns from cells, but we might have one less.  Handle the latter
        // case as follows: First ask the cellmap to drop its last col if it
        // doesn't have any actual cells in it.  Then call
        // MatchCellMapToColCache to append an anonymous column if it's needed;
        // this needs to be after RemoveColsAtEnd, since it will determine the
        // need for a new column frame based on the width of the cell map.
        cellMap->RemoveColsAtEnd();
        MatchCellMapToColCache(cellMap);
      }
    }
  }
  // for now, just bail and recalc all of the collapsing borders
  if (IsBorderCollapse()) {
    TableArea damageArea(0, 0, GetColCount(), GetRowCount());
    AddBCDamageArea(damageArea);
  }
}

/** Get the cell map for this table frame.  It is not always mCellMap.
 * Only the first-in-flow has a legit cell map.
 */

nsTableCellMap* nsTableFrame::GetCellMap() const {
  return static_cast<nsTableFrame*>(FirstInFlow())->mCellMap.get();
}

nsTableColGroupFrame* nsTableFrame::CreateSyntheticColGroupFrame() {
  nsIContent* colGroupContent = GetContent();
  mozilla::PresShell* presShell = PresShell();

  RefPtr<ComputedStyle> colGroupStyle;
  colGroupStyle = presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
      PseudoStyleType::tableColGroup);
  // Create a col group frame
  nsTableColGroupFrame* newFrame =
      NS_NewTableColGroupFrame(presShell, colGroupStyle);
  newFrame->SetIsSynthetic();
  newFrame->Init(colGroupContent, this, nullptr);
  return newFrame;
}

void nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) {
  MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");
  // get the last col group frame
  nsTableColGroupFrame* colGroupFrame =
      static_cast<nsTableColGroupFrame*>(mColGroups.LastChild());

  if (!colGroupFrame || !colGroupFrame->IsSynthetic()) {
    int32_t colIndex = (colGroupFrame) ? colGroupFrame->GetStartColumnIndex() +
                                             colGroupFrame->GetColCount()
                                       : 0;
    colGroupFrame = CreateSyntheticColGroupFrame();
    if (!colGroupFrame) {
      return;
    }
    // add the new frame to the child list
    mColGroups.AppendFrame(this, colGroupFrame);
    colGroupFrame->SetStartColumnIndex(colIndex);
  }
  AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell,
                           true);
}

// XXX this needs to be moved to nsCSSFrameConstructor
// Right now it only creates the col frames at the end
void nsTableFrame::AppendAnonymousColFrames(
    nsTableColGroupFrame* aColGroupFrame, int32_t aNumColsToAdd,
    nsTableColType aColType, bool aAddToTable) {
  MOZ_ASSERT(aColGroupFrame, "null frame");
  MOZ_ASSERT(aColType != eColAnonymousCol, "Shouldn't happen");
  MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");

  mozilla::PresShell* presShell = PresShell();

  // Get the last col frame
  nsFrameList newColFrames;

  int32_t startIndex = mColFrames.Length();
  int32_t lastIndex = startIndex + aNumColsToAdd - 1;

  for (int32_t childX = startIndex; childX <= lastIndex; childX++) {
    // all anonymous cols that we create here use a pseudo ComputedStyle of the
    // col group
    nsIContent* iContent = aColGroupFrame->GetContent();
    RefPtr<ComputedStyle> computedStyle =
        presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
            PseudoStyleType::tableCol);
    // ASSERTION to check for bug 54454 sneaking back in...
    NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames");

    // create the new col frame
    nsIFrame* colFrame = NS_NewTableColFrame(presShell, computedStyle);
    ((nsTableColFrame*)colFrame)->SetColType(aColType);
    colFrame->Init(iContent, aColGroupFrame, nullptr);

    newColFrames.AppendFrame(nullptr, colFrame);
  }
  nsFrameList& cols = aColGroupFrame->GetWritableChildList();
  nsIFrame* oldLastCol = cols.LastChild();
  const nsFrameList::Slice& newCols =
      cols.InsertFrames(nullptr, oldLastCol, std::move(newColFrames));
  if (aAddToTable) {
    // get the starting col index in the cache
    int32_t startColIndex;
    if (oldLastCol) {
      startColIndex =
          static_cast<nsTableColFrame*>(oldLastCol)->GetColIndex() + 1;
    } else {
      startColIndex = aColGroupFrame->GetStartColumnIndex();
    }

    aColGroupFrame->AddColsToTable(startColIndex, true, newCols);
  }
}

void nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) {
  int32_t numColsInMap = GetColCount();
  int32_t numColsInCache = mColFrames.Length();
  int32_t numColsToAdd = numColsInMap - numColsInCache;
  if (numColsToAdd > 0) {
    // this sets the child list, updates the col cache and cell map
    AppendAnonymousColFrames(numColsToAdd);
  }
  if (numColsToAdd < 0) {
    int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd);
    // if the cell map has fewer cols than the cache, correct it
    if (numColsNotRemoved > 0) {
      aCellMap->AddColsAtEnd(numColsNotRemoved);
    }
  }
}

void nsTableFrame::DidResizeColumns() {
  MOZ_ASSERT(!GetPrevInFlow(), "should only be called on first-in-flow");

  if (mBits.mResizedColumns) {
    return;  // already marked
  }

  for (nsTableFrame* f = this; f;
       f = static_cast<nsTableFrame*>(f->GetNextInFlow())) {
    f->mBits.mResizedColumns = true;
  }
}

void nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, int32_t aRowIndex) {
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    TableArea damageArea(0, 0, 0, 0);
    cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea);
    MatchCellMapToColCache(cellMap);
    if (IsBorderCollapse()) {
      AddBCDamageArea(damageArea);
    }
  }
}

void nsTableFrame::InsertCells(nsTArray<nsTableCellFrame*>& aCellFrames,
                               int32_t aRowIndex, int32_t aColIndexBefore) {
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    TableArea damageArea(0, 0, 0, 0);
    cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore, damageArea);
    MatchCellMapToColCache(cellMap);
    if (IsBorderCollapse()) {
      AddBCDamageArea(damageArea);
    }
  }
}

// this removes the frames from the col group and table, but not the cell map
int32_t nsTableFrame::DestroyAnonymousColFrames(int32_t aNumFrames) {
  // only remove cols that are of type eTypeAnonymous cell (they are at the end)
  int32_t endIndex = mColFrames.Length() - 1;
  int32_t startIndex = (endIndex - aNumFrames) + 1;
  int32_t numColsRemoved = 0;
  DestroyContext context(PresShell());
  for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) {
    nsTableColFrame* colFrame = GetColFrame(colIdx);
    if (colFrame && (eColAnonymousCell == colFrame->GetColType())) {
      auto* cgFrame = static_cast<nsTableColGroupFrame*>(colFrame->GetParent());
      // remove the frame from the colgroup
      cgFrame->RemoveChild(context, *colFrame, false);
      // remove the frame from the cache, but not the cell map
      RemoveCol(nullptr, colIdx, truefalse);
      numColsRemoved++;
    } else {
      break;
    }
  }
  return (aNumFrames - numColsRemoved);
}

void nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, int32_t aRowIndex) {
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    TableArea damageArea(0, 0, 0, 0);
    cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea);
    MatchCellMapToColCache(cellMap);
    if (IsBorderCollapse()) {
      AddBCDamageArea(damageArea);
    }
  }
}

int32_t nsTableFrame::GetStartRowIndex(
    const nsTableRowGroupFrame* aRowGroupFrame) const {
  RowGroupArray orderedRowGroups = OrderedRowGroups();

  int32_t rowIndex = 0;
  for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
    nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
    if (rgFrame == aRowGroupFrame) {
      break;
    }
    int32_t numRows = rgFrame->GetRowCount();
    rowIndex += numRows;
  }
  return rowIndex;
}

// this cannot extend beyond a single row group
void nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame,
                              int32_t aRowIndex,
                              nsTArray<nsTableRowFrame*>& aRowFrames) {
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex;
    InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true);
  }
}

// this cannot extend beyond a single row group
int32_t nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame,
                                 nsTArray<nsTableRowFrame*>& aRowFrames,
                                 int32_t aRowIndex, bool aConsiderSpans) {
#ifdef DEBUG_TABLE_CELLMAP
  printf("=== insertRowsBefore firstRow=%d \n", aRowIndex);
  Dump(truefalsetrue);
#endif

  int32_t numColsToAdd = 0;
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    TableArea damageArea(0, 0, 0, 0);
    bool shouldRecalculateIndex = !IsDeletedRowIndexRangesEmpty();
    if (shouldRecalculateIndex) {
      ResetRowIndices(nsFrameList::Slice(nullptr, nullptr));
    }
    int32_t origNumRows = cellMap->GetRowCount();
    int32_t numNewRows = aRowFrames.Length();
    cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans,
                        damageArea);
    MatchCellMapToColCache(cellMap);

    // Perform row index adjustment only if row indices were not
    // reset above
    if (!shouldRecalculateIndex) {
      if (aRowIndex < origNumRows) {
        AdjustRowIndices(aRowIndex, numNewRows);
      }

      // assign the correct row indices to the new rows. If they were
      // recalculated above it may not have been done correctly because each row
      // is constructed with index 0
      for (int32_t rowB = 0; rowB < numNewRows; rowB++) {
        nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowB);
        rowFrame->SetRowIndex(aRowIndex + rowB);
      }
    }

    if (IsBorderCollapse()) {
      AddBCDamageArea(damageArea);
    }
  }
#ifdef DEBUG_TABLE_CELLMAP
  printf("=== insertRowsAfter \n");
  Dump(truefalsetrue);
#endif

  return numColsToAdd;
}

void nsTableFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) {
  if (mDeletedRowIndexRanges.empty()) {
    mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
        aDeletedRowStoredIndex, aDeletedRowStoredIndex));
    return;
  }

  // Find the position of the current deleted row's stored index
  // among the previous deleted row index ranges and merge ranges if
  // they are consecutive, else add a new (disjoint) range to the map.
  // Call to mDeletedRowIndexRanges.upper_bound is
  // O(log(mDeletedRowIndexRanges.size())) therefore call to
  // AddDeletedRowIndex is also ~O(log(mDeletedRowIndexRanges.size()))

  // greaterIter = will point to smallest range in the map with lower value
  //              greater than the aDeletedRowStoredIndex.
  //              If no such value exists, point to end of map.
  // smallerIter = will point to largest range in the map with higher value
  //              smaller than the aDeletedRowStoredIndex
  //              If no such value exists, point to beginning of map.
  // i.e. when both values exist below is true:
  // smallerIter->second < aDeletedRowStoredIndex < greaterIter->first
  auto greaterIter = mDeletedRowIndexRanges.upper_bound(aDeletedRowStoredIndex);
  auto smallerIter = greaterIter;

  if (smallerIter != mDeletedRowIndexRanges.begin()) {
    smallerIter--;
    // While greaterIter might be out-of-bounds (by being equal to end()),
    // smallerIter now cannot be, since we returned early above for a 0-size
    // map.
  }

  // Note: smallerIter can only be equal to greaterIter when both
  // of them point to the beginning of the map and in that case smallerIter
  // does not "exist" but we clip smallerIter to point to beginning of map
  // so that it doesn't point to something unknown or outside the map boundry.
  // Note: When greaterIter is not the end (i.e. it "exists") upper_bound()
  // ensures aDeletedRowStoredIndex < greaterIter->first so no need to
  // assert that.
  MOZ_ASSERT(smallerIter == greaterIter ||
                 aDeletedRowStoredIndex > smallerIter->second,
             "aDeletedRowIndexRanges already contains aDeletedRowStoredIndex! "
             "Trying to delete an already deleted row?");

  if (smallerIter->second == aDeletedRowStoredIndex - 1) {
    if (greaterIter != mDeletedRowIndexRanges.end() &&
        greaterIter->first == aDeletedRowStoredIndex + 1) {
      // merge current index with smaller and greater range as they are
      // consecutive
      smallerIter->second = greaterIter->second;
      mDeletedRowIndexRanges.erase(greaterIter);
    } else {
      // add aDeletedRowStoredIndex in the smaller range as it is consecutive
      smallerIter->second = aDeletedRowStoredIndex;
    }
  } else if (greaterIter != mDeletedRowIndexRanges.end() &&
             greaterIter->first == aDeletedRowStoredIndex + 1) {
    // add aDeletedRowStoredIndex in the greater range as it is consecutive
    mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
        aDeletedRowStoredIndex, greaterIter->second));
    mDeletedRowIndexRanges.erase(greaterIter);
  } else {
    // add new range as aDeletedRowStoredIndex is disjoint from existing ranges
    mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
        aDeletedRowStoredIndex, aDeletedRowStoredIndex));
  }
}

int32_t nsTableFrame::GetAdjustmentForStoredIndex(int32_t aStoredIndex) {
  if (mDeletedRowIndexRanges.empty()) {
    return 0;
  }

  int32_t adjustment = 0;

  // O(log(mDeletedRowIndexRanges.size()))
  auto endIter = mDeletedRowIndexRanges.upper_bound(aStoredIndex);
  for (auto iter = mDeletedRowIndexRanges.begin(); iter != endIter; ++iter) {
    adjustment += iter->second - iter->first + 1;
  }

  return adjustment;
}

// this cannot extend beyond a single row group
void nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame,
                              int32_t aNumRowsToRemove, bool aConsiderSpans) {
#ifdef TBD_OPTIMIZATION
  // decide if we need to rebalance. we have to do this here because the row
  // group cannot do it when it gets the dirty reflow corresponding to the frame
  // being destroyed
  bool stopTelling = false;
  for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking);
       kidFrame = kidFrame->GetNextSibling()) {
    nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
    if (cellFrame) {
      stopTelling = tableFrame->CellChangedWidth(
          *cellFrame, cellFrame->GetPass1MaxElementWidth(),
          cellFrame->GetMaximumWidth(), true);
    }
  }
  // XXX need to consider what happens if there are cells that have rowspans
  // into the deleted row. Need to consider moving rows if a rebalance doesn't
  // happen
#endif

  int32_t firstRowIndex = aFirstRowFrame.GetRowIndex();
#ifdef DEBUG_TABLE_CELLMAP
  printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex,
         aNumRowsToRemove);
  Dump(truefalsetrue);
#endif
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    TableArea damageArea(0, 0, 0, 0);

    // Mark rows starting from aFirstRowFrame to the next 'aNumRowsToRemove-1'
    // number of rows as deleted.
    nsTableRowGroupFrame* parentFrame = aFirstRowFrame.GetTableRowGroupFrame();
    parentFrame->MarkRowsAsDeleted(aFirstRowFrame, aNumRowsToRemove);

    cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans,
                        damageArea);
    MatchCellMapToColCache(cellMap);
    if (IsBorderCollapse()) {
      AddBCDamageArea(damageArea);
    }
  }

#ifdef DEBUG_TABLE_CELLMAP
  printf("=== removeRowsAfter\n");
  Dump(truetruetrue);
#endif
}

// collect the rows ancestors of aFrame
int32_t nsTableFrame::CollectRows(nsIFrame* aFrame,
                                  nsTArray<nsTableRowFrame*>& aCollection) {
  MOZ_ASSERT(aFrame, "null frame");
  int32_t numRows = 0;
  for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
    aCollection.AppendElement(static_cast<nsTableRowFrame*>(childFrame));
    numRows++;
  }
  return numRows;
}

void nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) {
#ifdef DEBUG_TABLE_CELLMAP
  printf("=== insertRowGroupsBefore\n");
  Dump(truefalsetrue);
#endif
  nsTableCellMap* cellMap = GetCellMap();
  if (cellMap) {
    RowGroupArray orderedRowGroups = OrderedRowGroups();

    AutoTArray<nsTableRowFrame*, 8> rows;
    // Loop over the rowgroups and check if some of them are new, if they are
    // insert cellmaps in the order that is predefined by OrderedRowGroups.
    // XXXbz this code is O(N*M) where N is number of new rowgroups
    // and M is number of rowgroups we have!
    uint32_t rgIndex;
    for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
      for (nsIFrame* rowGroup : aRowGroups) {
        if (orderedRowGroups[rgIndex] == rowGroup) {
          nsTableRowGroupFrame* priorRG =
              (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
          // create and add the cell map for the row group
          cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG);

          break;
        }
      }
    }
    cellMap->Synchronize(this);
    ResetRowIndices(aRowGroups);

    // now that the cellmaps are reordered too insert the rows
    for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
      for (nsIFrame* rowGroup : aRowGroups) {
        if (orderedRowGroups[rgIndex] == rowGroup) {
          nsTableRowGroupFrame* priorRG =
              (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
          // collect the new row frames in an array and add them to the table
          int32_t numRows = CollectRows(rowGroup, rows);
          if (numRows > 0) {
            int32_t rowIndex = 0;
            if (priorRG) {
              int32_t priorNumRows = priorRG->GetRowCount();
              rowIndex = priorRG->GetStartRowIndex() + priorNumRows;
            }
            InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true);
            rows.Clear();
          }
          break;
        }
      }
    }
  }
#ifdef DEBUG_TABLE_CELLMAP
  printf("=== insertRowGroupsAfter\n");
  Dump(truetruetrue);
#endif
}

/////////////////////////////////////////////////////////////////////////////
// Child frame enumeration

const nsFrameList& nsTableFrame::GetChildList(ChildListID aListID) const {
  if (aListID == FrameChildListID::ColGroup) {
    return mColGroups;
  }
  return nsContainerFrame::GetChildList(aListID);
}

void nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
  nsContainerFrame::GetChildLists(aLists);
  mColGroups.AppendIfNonempty(aLists, FrameChildListID::ColGroup);
}

static inline bool FrameHasBorder(nsIFrame* f) {
  if (!f->StyleVisibility()->IsVisible()) {
    return false;
  }

  return f->StyleBorder()->HasBorder();
}

void nsTableFrame::CalcHasBCBorders() {
  if (!IsBorderCollapse()) {
    SetHasBCBorders(false);
    return;
  }

  if (FrameHasBorder(this)) {
    SetHasBCBorders(true);
    return;
  }

  // Check col and col group has borders.
  for (nsIFrame* f : this->GetChildList(FrameChildListID::ColGroup)) {
    if (FrameHasBorder(f)) {
      SetHasBCBorders(true);
      return;
    }

    nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(f);
    for (nsTableColFrame* col = colGroup->GetFirstColumn(); col;
         col = col->GetNextCol()) {
      if (FrameHasBorder(col)) {
        SetHasBCBorders(true);
        return;
      }
    }
  }

  // check row group, row and cell has borders.
  RowGroupArray rowGroups = OrderedRowGroups();
  for (nsTableRowGroupFrame* rowGroup : rowGroups) {
    if (FrameHasBorder(rowGroup)) {
      SetHasBCBorders(true);
      return;
    }

    for (nsTableRowFrame* row = rowGroup->GetFirstRow(); row;
         row = row->GetNextRow()) {
      if (FrameHasBorder(row)) {
        SetHasBCBorders(true);
        return;
      }

      for (nsTableCellFrame* cell = row->GetFirstCell(); cell;
           cell = cell->GetNextCell()) {
        if (FrameHasBorder(cell)) {
          SetHasBCBorders(true);
          return;
        }
      }
    }
  }

  SetHasBCBorders(false);
}

namespace mozilla {
class nsDisplayTableBorderCollapse;
}

// table paint code is concerned primarily with borders and bg color
// SEC: TODO: adjust the rect for captions
void nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                    const nsDisplayListSet& aLists) {
  DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255, 128, 255));

  DisplayBorderBackgroundOutline(aBuilder, aLists);

  nsDisplayTableBackgroundSet tableBGs(aBuilder, this);
  nsDisplayListCollection lists(aBuilder);

  // This is similar to what
  // nsContainerFrame::BuildDisplayListForNonBlockChildren does, except that we
  // allow the children's background and borders to go in our BorderBackground
  // list. This doesn't really affect background painting --- the children won't
  // actually draw their own backgrounds because the nsTableFrame already drew
  // them, unless a child has its own stacking context, in which case the child
  // won't use its passed-in BorderBackground list anyway. It does affect cell
  // borders though; this lets us get cell borders into the nsTableFrame's
  // BorderBackground list.
  for (nsIFrame* colGroup :
       FirstContinuation()->GetChildList(FrameChildListID::ColGroup)) {
    for (nsIFrame* col : colGroup->PrincipalChildList()) {
      tableBGs.AddColumn((nsTableColFrame*)col);
    }
  }

  for (nsIFrame* kid : PrincipalChildList()) {
    BuildDisplayListForChild(aBuilder, kid, lists);
  }

  tableBGs.MoveTo(aLists);
  lists.MoveTo(aLists);

  if (IsVisibleForPainting()) {
    // In the collapsed border model, overlay all collapsed borders.
    if (IsBorderCollapse()) {
      if (HasBCBorders()) {
        aLists.BorderBackground()->AppendNewToTop<nsDisplayTableBorderCollapse>(
            aBuilder, this);
      }
    } else {
      const nsStyleBorder* borderStyle = StyleBorder();
      if (borderStyle->HasBorder()) {
        aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder,
                                                                   this);
      }
    }
  }
}

LogicalSides nsTableFrame::GetLogicalSkipSides() const {
  LogicalSides skip(mWritingMode);
  if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
                   StyleBoxDecorationBreak::Clone)) {
    return skip;
  }

  // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto
  // account for pagination
  if (GetPrevInFlow()) {
    skip += LogicalSide::BStart;
  }
  if (GetNextInFlow()) {
    skip += LogicalSide::BEnd;
  }
  return skip;
}

void nsTableFrame::SetColumnDimensions(nscoord aBSize, WritingMode aWM,
                                       const LogicalMargin& aBorderPadding,
                                       const nsSize& aContainerSize) {
  const nscoord colBSize =
      aBSize - (aBorderPadding.BStartEnd(aWM) + GetRowSpacing(-1) +
                GetRowSpacing(GetRowCount()));
  int32_t colIdx = 0;
  LogicalPoint colGroupOrigin(aWM,
                              aBorderPadding.IStart(aWM) + GetColSpacing(-1),
                              aBorderPadding.BStart(aWM) + GetRowSpacing(-1));
  nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
  for (nsIFrame* colGroupFrame : mColGroups) {
    MOZ_ASSERT(colGroupFrame->IsTableColGroupFrame());
    // first we need to figure out the size of the colgroup
    int32_t groupFirstCol = colIdx;
    nscoord colGroupISize = 0;
    nscoord colSpacing = 0;
    const nsFrameList& columnList = colGroupFrame->PrincipalChildList();
    for (nsIFrame* colFrame : columnList) {
      if (mozilla::StyleDisplay::TableColumn ==
          colFrame->StyleDisplay()->mDisplay) {
        NS_ASSERTION(colIdx < GetColCount(), "invalid number of columns");
        colSpacing = GetColSpacing(colIdx);
        colGroupISize +=
            fif->GetColumnISizeFromFirstInFlow(colIdx) + colSpacing;
        ++colIdx;
      }
    }
    if (colGroupISize) {
      colGroupISize -= colSpacing;
    }

    LogicalRect colGroupRect(aWM, colGroupOrigin.I(aWM), colGroupOrigin.B(aWM),
                             colGroupISize, colBSize);
    colGroupFrame->SetRect(aWM, colGroupRect, aContainerSize);
    nsSize colGroupSize = colGroupFrame->GetSize();

    // then we can place the columns correctly within the group
    colIdx = groupFirstCol;
    LogicalPoint colOrigin(aWM);
    for (nsIFrame* colFrame : columnList) {
      if (mozilla::StyleDisplay::TableColumn ==
          colFrame->StyleDisplay()->mDisplay) {
        nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
        LogicalRect colRect(aWM, colOrigin.I(aWM), colOrigin.B(aWM), colISize,
                            colBSize);
        colFrame->SetRect(aWM, colRect, colGroupSize);
        colSpacing = GetColSpacing(colIdx);
        colOrigin.I(aWM) += colISize + colSpacing;
        ++colIdx;
      }
    }

    colGroupOrigin.I(aWM) += colGroupISize + colSpacing;
  }
}

// SEC: TODO need to worry about continuing frames prev/next in flow for
// splitting across pages.

// XXX this could be made more general to handle row modifications that change
// the table bsize, but first we need to scrutinize every Invalidate
void nsTableFrame::ProcessRowInserted(nscoord aNewBSize) {
  SetRowInserted(false);  // reset the bit that got us here
  RowGroupArray rowGroups = OrderedRowGroups();
  // find the row group containing the inserted row
  for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
    nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
    NS_ASSERTION(rgFrame, "Must have rgFrame here");
    // find the row that was inserted first
    for (nsIFrame* childFrame : rgFrame->PrincipalChildList()) {
      nsTableRowFrame* rowFrame = do_QueryFrame(childFrame);
      if (rowFrame) {
        if (rowFrame->IsFirstInserted()) {
          rowFrame->SetFirstInserted(false);
          // damage the table from the 1st row inserted to the end of the table
          nsIFrame::InvalidateFrame();
          // XXXbz didn't we do this up front?  Why do we need to do it again?
          SetRowInserted(false);
          return;  // found it, so leave
        }
      }
    }
  }
}

/* virtual */
void nsTableFrame::MarkIntrinsicISizesDirty() {
  nsITableLayoutStrategy* tls = LayoutStrategy();
  if (MOZ_UNLIKELY(!tls)) {
    // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame()
    // walking up the ancestor chain in a table next-in-flow.  In this case
    // our original first-in-flow (which owns the TableLayoutStrategy) has
    // already been destroyed and unhooked from the flow chain and thusly
    // LayoutStrategy() returns null.  All the frames in the flow will be
    // destroyed so no need to mark anything dirty here.  See bug 595758.
    return;
  }
  tls->MarkIntrinsicISizesDirty();

  // XXXldb Call SetBCDamageArea?

  nsContainerFrame::MarkIntrinsicISizesDirty();
}

nscoord nsTableFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
                                     IntrinsicISizeType aType) {
  if (NeedToCalcBCBorders()) {
    CalcBCBorders();
  }

  ReflowColGroups(aInput.mContext);

  return aType == IntrinsicISizeType::MinISize
             ? LayoutStrategy()->GetMinISize(aInput.mContext)
             : LayoutStrategy()->GetPrefISize(aInput.mContext, false);
}

/* virtual */ nsIFrame::IntrinsicSizeOffsetData
nsTableFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) {
  IntrinsicSizeOffsetData result =
      nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis);

  result.margin = 0;

  if (IsBorderCollapse()) {
    result.padding = 0;

    WritingMode wm = GetWritingMode();
    LogicalMargin outerBC = GetOuterBCBorder(wm);
    result.border = outerBC.IStartEnd(wm);
  }

  return result;
}

/* virtual */
nsIFrame::SizeComputationResult nsTableFrame::ComputeSize(
    gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
    nscoord aAvailableISize, const LogicalSize& aMargin,
    const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
    ComputeSizeFlags aFlags) {
  // Only table wrapper calls this method, and it should use our writing mode.
  MOZ_ASSERT(aWM == GetWritingMode(),
             "aWM should be the same as our writing mode!");

  auto result = nsContainerFrame::ComputeSize(
      aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding,
      aSizeOverrides, aFlags);

  // If our containing block wants to override inner table frame's inline-size
  // (e.g. when resolving flex base size), don't enforce the min inline-size
  // later in this method.
  if (aSizeOverrides.mApplyOverridesVerbatim && aSizeOverrides.mStyleISize &&
      aSizeOverrides.mStyleISize->IsLengthPercentage()) {
    return result;
  }

  // If we're a container for font size inflation, then shrink
  // wrapping inside of us should not apply font size inflation.
  AutoMaybeDisableFontInflation an(this);

  // Tables never shrink below their min inline-size.
  const IntrinsicSizeInput input(aRenderingContext, Some(aCBSize), Nothing());
  nscoord minISize = GetMinISize(input);
  if (minISize > result.mLogicalSize.ISize(aWM)) {
    result.mLogicalSize.ISize(aWM) = minISize;
  }

  return result;
}

nscoord nsTableFrame::TableShrinkISizeToFit(gfxContext* aRenderingContext,
                                            nscoord aISizeInCB) {
  // If we're a container for font size inflation, then shrink
  // wrapping inside of us should not apply font size inflation.
  AutoMaybeDisableFontInflation an(this);

  nscoord result;
  const IntrinsicSizeInput input(aRenderingContext, Nothing(), Nothing());
  nscoord minISize = GetMinISize(input);
  if (minISize > aISizeInCB) {
    result = minISize;
  } else {
    // Tables shrink inline-size to fit with a slightly different algorithm
    // from the one they use for their intrinsic isize (the difference
    // relates to handling of percentage isizes on columns).  So this
    // function differs from nsIFrame::ShrinkISizeToFit by only the
    // following line.
    // Since we've already called GetMinISize, we don't need to do any
    // of the other stuff GetPrefISize does.
    nscoord prefISize = LayoutStrategy()->GetPrefISize(aRenderingContext, true);
    if (prefISize > aISizeInCB) {
      result = aISizeInCB;
    } else {
      result = prefISize;
    }
  }
  return result;
}

/* virtual */
LogicalSize nsTableFrame::ComputeAutoSize(
    gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
    nscoord aAvailableISize, const LogicalSize& aMargin,
    const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
    ComputeSizeFlags aFlags) {
  // Tables always shrink-wrap.
  nscoord cbBased =
      aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
  return LogicalSize(aWM, TableShrinkISizeToFit(aRenderingContext, cbBased),
                     NS_UNCONSTRAINEDSIZE);
}

// Return true if aParentReflowInput.frame or any of its ancestors within
// the containing table have non-auto bsize. (e.g. pct or fixed bsize)
bool nsTableFrame::AncestorsHaveStyleBSize(
    const ReflowInput& aParentReflowInput) {
  WritingMode wm = aParentReflowInput.GetWritingMode();
  for (const ReflowInput* rs = &aParentReflowInput; rs && rs->mFrame;
       rs = rs->mParentReflowInput) {
    LayoutFrameType frameType = rs->mFrame->Type();
    if (LayoutFrameType::TableCell == frameType ||
        LayoutFrameType::TableRow == frameType ||
        LayoutFrameType::TableRowGroup == frameType) {
      const auto& bsize = rs->mStylePosition->BSize(wm);
      // calc() with both lengths and percentages treated like 'auto' on
      // internal table elements
      if (!bsize.IsAuto() && !bsize.HasLengthAndPercentage()) {
        return true;
      }
    } else if (LayoutFrameType::Table == frameType) {
      // we reached the containing table, so always return
      return !rs->mStylePosition->BSize(wm).IsAuto();
    }
  }
  return false;
}

// See if a special block-size reflow needs to occur and if so,
// call RequestSpecialBSizeReflow
void nsTableFrame::CheckRequestSpecialBSizeReflow(
    const ReflowInput& aReflowInput) {
  NS_ASSERTION(aReflowInput.mFrame->IsTableCellFrame() ||
                   aReflowInput.mFrame->IsTableRowFrame() ||
                   aReflowInput.mFrame->IsTableRowGroupFrame() ||
                   aReflowInput.mFrame->IsTableFrame(),
               "unexpected frame type");
  WritingMode wm = aReflowInput.GetWritingMode();
  if (!aReflowInput.mFrame->GetPrevInFlow() &&  // 1st in flow
      (NS_UNCONSTRAINEDSIZE ==
           aReflowInput.ComputedBSize() ||  // no computed bsize
       0 == aReflowInput.ComputedBSize()) &&
      aReflowInput.mStylePosition->BSize(wm)
          .ConvertsToPercentage() &&  // pct bsize
      nsTableFrame::AncestorsHaveStyleBSize(*aReflowInput.mParentReflowInput)) {
    nsTableFrame::RequestSpecialBSizeReflow(aReflowInput);
  }
}

// Notify the frame and its ancestors (up to the containing table) that a
// special bsize reflow will occur. During a special bsize reflow, a table, row
// group, row, or cell returns the last size it was reflowed at. However, the
// table may change the bsize of row groups, rows, cells in
// DistributeBSizeToRows after. And the row group can change the bsize of rows,
// cells in CalculateRowBSizes.
void nsTableFrame::RequestSpecialBSizeReflow(const ReflowInput& aReflowInput) {
  // notify the frame and its ancestors of the special reflow, stopping at the
  // containing table
  for (const ReflowInput* rs = &aReflowInput; rs && rs->mFrame;
       rs = rs->mParentReflowInput) {
    LayoutFrameType frameType = rs->mFrame->Type();
    NS_ASSERTION(LayoutFrameType::TableCell == frameType ||
                     LayoutFrameType::TableRow == frameType ||
                     LayoutFrameType::TableRowGroup == frameType ||
                     LayoutFrameType::Table == frameType,
                 "unexpected frame type");

    rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
    if (LayoutFrameType::Table == frameType) {
      NS_ASSERTION(rs != &aReflowInput,
                   "should not request special bsize reflow for table");
      // always stop when we reach a table
      break;
    }
  }
}

/******************************************************************************************
 * Before reflow, intrinsic inline-size calculation is done using GetMinISize
 * and GetPrefISize.  This used to be known as pass 1 reflow.
 *
 * After the intrinsic isize calculation, the table determines the
 * column widths using BalanceColumnISizes() and
 * then reflows each child again with a constrained avail isize. This reflow is
 * referred to as the pass 2 reflow.
 *
 * A special bsize reflow (pass 3 reflow) can occur during an initial or resize
 * reflow if (a) a row group, row, cell, or a frame inside a cell has a percent
 * bsize but no computed bsize or (b) in paginated mode, a table has a bsize.
 * (a) supports percent nested tables contained inside cells whose bsizes aren't
 * known until after the pass 2 reflow. (b) is necessary because the table
 * cannot split until after the pass 2 reflow. The mechanics of the special
 * bsize reflow (variety a) are as follows:
 *
 * 1) Each table related frame (table, row group, row, cell) implements
 *    NeedsSpecialReflow() to indicate that it should get the reflow. It does
 *    this when it has a percent bsize but no computed bsize by calling
 *    CheckRequestSpecialBSizeReflow(). This method calls
 *    RequestSpecialBSizeReflow() which calls SetNeedSpecialReflow() on its
 *    ancestors until it reaches the containing table and calls
 *    SetNeedToInitiateSpecialReflow() on it. For percent bsize frames inside
 *    cells, during DidReflow(), the cell's NotifyPercentBSize() is called
 *    (the cell is the reflow input's mPercentBSizeObserver in this case).
 *    NotifyPercentBSize() calls RequestSpecialBSizeReflow().
 *
 * XXX (jfkthame) This comment appears to be out of date; it refers to
 * methods/flags that are no longer present in the code.
 *
 * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true)
 *    was called, it will do the special bsize reflow, setting the reflow
 *    input's mFlags.mSpecialBSizeReflow to true and mSpecialHeightInitiator to
 *    itself. It won't do this if IsPrematureSpecialHeightReflow() returns true
 *    because in that case another special bsize reflow will be coming along
 *    with the containing table as the mSpecialHeightInitiator. It is only
 *    relevant to do the reflow when the mSpecialHeightInitiator is the
 *    containing table, because if it is a remote ancestor, then appropriate
 *    bsizes will not be known.
 *
 * 3) Since the bsizes of the table, row groups, rows, and cells was determined
 *    during the pass 2 reflow, they return their last desired sizes during the
 *    special bsize reflow. The reflow only permits percent bsize frames inside
 *    the cells to resize based on the cells bsize and that bsize was
 *    determined during the pass 2 reflow.
 *
 * So, in the case of deeply nested tables, all of the tables that were told to
 * initiate a special reflow will do so, but if a table is already in a special
 * reflow, it won't inititate the reflow until the current initiator is its
 * containing table. Since these reflows are only received by frames that need
 * them and they don't cause any rebalancing of tables, the extra overhead is
 * minimal.
 *
 * The type of special reflow that occurs during printing (variety b) follows
 * the same mechanism except that all frames will receive the reflow even if
 * they don't really need them.
 *
 * Open issues with the special bsize reflow:
 *
 * 1) At some point there should be 2 kinds of special bsize reflows because (a)
 *    and (b) above are really quite different. This would avoid unnecessary
 *    reflows during printing.
 *
 * 2) When a cell contains frames whose percent bsizes > 100%, there is data
 *    loss (see bug 115245). However, this can also occur if a cell has a fixed
 *    bsize and there is no special bsize reflow.
 *
 * XXXldb Special bsize reflow should really be its own method, not
 * part of nsIFrame::Reflow.  It should then call nsIFrame::Reflow on
 * the contents of the cells to do the necessary block-axis resizing.
 *
 ******************************************************************************************/


/* Layout the entire inner table. */
void nsTableFrame::Reflow(nsPresContext* aPresContext,
                          ReflowOutput& aDesiredSize,
                          const ReflowInput& aReflowInput,
                          nsReflowStatus& aStatus) {
  MarkInReflow();
  DO_GLOBAL_REFLOW_COUNT("nsTableFrame");
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
             "The nsTableWrapperFrame should be the out-of-flow if needed");

  const WritingMode wm = aReflowInput.GetWritingMode();
  MOZ_ASSERT(aReflowInput.ComputedLogicalMargin(wm).IsAllZero(),
             "Only nsTableWrapperFrame can have margins!");

  bool isPaginated = aPresContext->IsPaginated();

  if (!GetPrevInFlow() && !mTableLayoutStrategy) {
    NS_ERROR("strategy should have been created in Init");
    return;
  }

  // see if collapsing borders need to be calculated
  if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) {
    CalcBCBorders();
  }

  // Check for an overflow list, and append any row group frames being pushed
  MoveOverflowToChildList();

  bool haveCalledCalcDesiredBSize = false;
  SetHaveReflowedColGroups(false);

  LogicalMargin borderPadding =
      aReflowInput.ComputedLogicalBorderPadding(wm).ApplySkipSides(
          PreReflowBlockLevelLogicalSkipSides());
  nsIFrame* lastChildReflowed = nullptr;
  const nsSize containerSize =
      aReflowInput.ComputedSizeAsContainerIfConstrained();

  // The tentative width is the width we assumed for the table when the child
  // frames were positioned (which only matters in vertical-rl mode, because
  // they're positioned relative to the right-hand edge). Then, after reflowing
  // the kids, we can check whether the table ends up with a different width
  // than this tentative value (either because it was unconstrained, so we used
  // zero, or because it was enlarged by the child frames), we make the
  // necessary positioning adjustments along the x-axis.
  nscoord tentativeContainerWidth = 0;
  bool mayAdjustXForAllChildren = false;

  // Reflow the entire table (pass 2 and possibly pass 3). This phase is
  // necessary during a constrained initial reflow and other reflows which
  // require either a strategy init or balance. This isn't done during an
  // unconstrained reflow, because it will occur later when the parent reflows
  // with a constrained isize.
  if (IsSubtreeDirty() || aReflowInput.ShouldReflowAllKids() ||
      IsGeometryDirty() || isPaginated || aReflowInput.IsBResize() ||
      NeedToCollapse()) {
    if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
        // Also check IsBResize(), to handle the first Reflow preceding a
        // special bsize Reflow, when we've already had a special bsize
        // Reflow (where ComputedBSize() would not be
        // NS_UNCONSTRAINEDSIZE, but without a style change in between).
        aReflowInput.IsBResize()) {
      // XXX Eventually, we should modify DistributeBSizeToRows to use
      // nsTableRowFrame::GetInitialBSize instead of nsIFrame::BSize().
      // That way, it will make its calculations based on internal table
      // frame bsizes as they are before they ever had any extra bsize
      // distributed to them.  In the meantime, this reflows all the
      // internal table frames, which restores them to their state before
      // DistributeBSizeToRows was called.
      SetGeometryDirty();
    }

    bool needToInitiateSpecialReflow = false;
    if (isPaginated) {
      // see if an extra reflow will be necessary in pagination mode
      // when there is a specified table bsize
      if (!GetPrevInFlow() &&
          NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize()) {
        nscoord tableSpecifiedBSize = CalcBorderBoxBSize(
            aReflowInput, borderPadding, NS_UNCONSTRAINEDSIZE);
        if (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE &&
            tableSpecifiedBSize > 0) {
          needToInitiateSpecialReflow = true;
        }
      }
    } else {
      needToInitiateSpecialReflow =
          HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
    }

    NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow,
                 "Shouldn't be in special bsize reflow here!");

    const TableReflowMode firstReflowMode = needToInitiateSpecialReflow
                                                ? TableReflowMode::Measuring
                                                : TableReflowMode::Final;
    ReflowTable(aDesiredSize, aReflowInput, borderPadding, firstReflowMode,
                lastChildReflowed, aStatus);

    // When in vertical-rl mode, there may be two kinds of scenarios in which
    // the positioning of all the children need to be adjusted along the x-axis
    // because the width we assumed for the table when the child frames were
    // being positioned(i.e. tentative width) may be different from the final
    // width for the table:
    // 1. If the computed width for the table is unconstrained, a dummy zero
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=94 G=92

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.