Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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);
else
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);
else
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, true, false);
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(true, false, true);
#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(true, false, true);
#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(true, false, true);
#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(true, true, true);
#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(true, false, true);
#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(true, true, true);
#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();
}
/* virtual */
nscoord nsTableFrame::GetMinISize(gfxContext* aRenderingContext) {
if (NeedToCalcBCBorders()) CalcBCBorders();
ReflowColGroups(aRenderingContext);
return LayoutStrategy()->GetMinISize(aRenderingContext);
}
/* virtual */
nscoord nsTableFrame::GetPrefISize(gfxContext* aRenderingContext) {
if (NeedToCalcBCBorders()) CalcBCBorders();
ReflowColGroups(aRenderingContext);
return LayoutStrategy()->GetPrefISize(aRenderingContext, 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.
nscoord minISize = GetMinISize(aRenderingContext);
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;
nscoord minISize = GetMinISize(aRenderingContext);
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
// width was assumed as the tentative width to begin with.
// 2. If the child frames enlarge the width for the table, the final width
// becomes larger than the tentative one.
// Let's record the tentative width here, if later the final width turns out
// to be different from this tentative one, it means one of the above
// scenarios happens, then we adjust positioning of all the children.
// Note that vertical-lr, unlike vertical-rl, doesn't need to take special
// care of this situation, because they're positioned relative to the
// left-hand edge.
if (wm.IsVerticalRL()) {
tentativeContainerWidth = containerSize.width;
mayAdjustXForAllChildren = true;
}
// reevaluate special bsize reflow conditions
if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
needToInitiateSpecialReflow = true;
}
// XXXldb Are all these conditions correct?
if (needToInitiateSpecialReflow && aStatus.IsComplete()) {
// XXXldb Do we need to set the IsBResize flag on any reflow inputs?
ReflowInput& mutable_rs = const_cast<ReflowInput&>(aReflowInput);
// distribute extra block-direction space to rows
aDesiredSize.BSize(wm) =
CalcDesiredBSize(aReflowInput, borderPadding, aStatus);
haveCalledCalcDesiredBSize = true;