/* -*- 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 "mozilla/Likely.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/WritingModes.h"
#include "gfxContext.h"
#include "nsCOMPtr.h"
#include "nsTableFrame.h"
#include "nsStyleContext.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 "nsIPresShell.h"
#include "nsIDOMElement.h"
#include "nsIDOMHTMLElement.h"
#include "nsIScriptError.h"
#include "nsFrameManager.h"
#include "nsError.h"
#include "nsCSSFrameConstructor.h"
#include "mozilla/Range.h"
#include "mozilla/ServoRestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StyleSetHandle.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "nsDisplayList.h"
#include "nsIScrollableFrame.h"
#include "nsCSSProps.h"
#include "RestyleTracker.h"
#include "nsStyleChangeList.h"
#include <algorithm>
#include "gfxPrefs.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/WebRenderLayerManager.h"
using namespace mozilla;
using namespace mozilla::image;
using namespace mozilla::layout;
/********************************************************************************
** TableReflowInput **
********************************************************************************/
namespace mozilla {
struct TableReflowInput {
// the real reflow state
const ReflowInput& reflowInput;
// The table's available size (in reflowInput's writing mode)
LogicalSize availSize;
// Stationary inline-offset
nscoord iCoord;
// Running block-offset
nscoord bCoord;
TableReflowInput(const ReflowInput& aReflowInput,
const LogicalSize& aAvailSize)
: reflowInput(aReflowInput)
, availSize(aAvailSize)
{
MOZ_ASSERT(reflowInput.mFrame->IsTableFrame(),
"TableReflowInput should only be created for nsTableFrame");
nsTableFrame* table =
static_cast<nsTableFrame*>(reflowInput.mFrame->FirstInFlow());
WritingMode wm = aReflowInput.GetWritingMode();
LogicalMargin borderPadding = table->GetChildAreaOffset(wm, &reflowInput);
iCoord = borderPadding.IStart(wm) + table->GetColSpacing(-1);
bCoord = borderPadding.BStart(wm); //cellspacing added during reflow
// XXX do we actually need to check for unconstrained inline-size here?
if (NS_UNCONSTRAINEDSIZE != availSize.ISize(wm)) {
int32_t colCount = table->GetColCount();
availSize.ISize(wm) -= borderPadding.IStartEnd(wm) +
table->GetColSpacing(-1) +
table->GetColSpacing(colCount);
availSize.ISize(wm) = std::max(0, availSize.ISize(wm));
}
if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
availSize.BSize(wm) -= borderPadding.BStartEnd(wm) +
table->GetRowSpacing(-1) +
table->GetRowSpacing(table->GetRowCount());
availSize.BSize(wm) = std::max(0, availSize.BSize(wm));
}
}
};
} // namespace mozilla
/********************************************************************************
** nsTableFrame **
********************************************************************************/
struct BCPropertyData
{
BCPropertyData() : mBStartBorderWidth(0), mIEndBorderWidth(0),
mBEndBorderWidth(0), mIStartBorderWidth(0),
mIStartCellBorderWidth(0), mIEndCellBorderWidth(0) {}
TableArea mDamageArea;
BCPixelSize mBStartBorderWidth;
BCPixelSize mIEndBorderWidth;
BCPixelSize mBEndBorderWidth;
BCPixelSize mIStartBorderWidth;
BCPixelSize mIStartCellBorderWidth;
BCPixelSize mIEndCellBorderWidth;
};
nsStyleContext*
nsTableFrame::GetParentStyleContext(nsIFrame** aProviderFrame) const
{
// Since our parent, the table wrapper frame, returned this frame, we
// must return whatever our parent would normally have returned.
NS_PRECONDITION(GetParent(), "table constructed without table wrapper");
if (!mContent->GetParent() && !StyleContext()->GetPseudo()) {
// We're the root. We have no style context parent.
*aProviderFrame = nullptr;
return nullptr;
}
return GetParent()->DoGetParentStyleContext(aProviderFrame);
}
nsTableFrame::nsTableFrame(nsStyleContext* aContext, ClassID aID)
: nsContainerFrame(aContext, aID)
, mCellMap(nullptr)
, mTableLayoutStrategy(nullptr)
{
memset(&mBits, 0, sizeof(mBits));
}
void
nsTableFrame::Init(nsIContent* aContent,
nsContainerFrame* aParent,
nsIFrame* aPrevInFlow)
{
NS_PRECONDITION(!mCellMap, "Init called twice");
NS_PRECONDITION(!mTableLayoutStrategy, "Init called twice");
NS_PRECONDITION(!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 = (NS_STYLE_BORDER_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 = new nsTableCellMap(*this, borderCollapse);
if (IsAutoLayout()) {
mTableLayoutStrategy = new BasicTableLayoutStrategy(this);
} else {
mTableLayoutStrategy = new 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)));
}
}
nsTableFrame::~nsTableFrame()
{
delete mCellMap;
delete mTableLayoutStrategy;
}
void
nsTableFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
{
mColGroups.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}
// 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->mBreakAfter || (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->mBreakBefore ||
(nextRg && nextRg->HasInternalBreakBefore())) &&
!IsRepeatedFrame(aNextFrame)) {
return !IsRepeatedFrame(aSourceFrame); // or after
}
}
return false;
}
/* static */ void
nsTableFrame::RegisterPositionedTablePart(nsIFrame* aFrame)
{
// Supporting relative positioning for table parts other than table cells has
// the potential to break sites that apply 'position: relative' to those
// parts, expecting nothing to happen. We warn at the console to make tracking
// down the issue easy.
if (!IS_TABLE_CELL(aFrame->Type())) {
nsIContent* content = aFrame->GetContent();
nsPresContext* presContext = aFrame->PresContext();
if (content && !presContext->HasWarnedAboutPositionedTableParts()) {
presContext->SetHasWarnedAboutPositionedTableParts();
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Layout: Tables"),
content->OwnerDoc(),
nsContentUtils::eLAYOUT_PROPERTIES,
"TablePartRelPosWarning");
}
}
nsTableFrame* tableFrame = nsTableFrame::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);
}
// Add this frame to the list.
positionedParts->AppendElement(aFrame);
}
/* static */ void
nsTableFrame::UnregisterPositionedTablePart(nsIFrame* aFrame,
nsIFrame* aDestructRoot)
{
// Retrieve the table frame, and check if we hit aDestructRoot on the way.
bool didPassThrough;
nsTableFrame* tableFrame = GetTableFramePassingThrough(aDestructRoot, aFrame,
&didPassThrough);
if (!didPassThrough && !tableFrame->GetPrevContinuation()) {
// The table frame will be destroyed, and it's the first im flow (and thus
// owning the PositionedTablePartArray), so we don't need to do
// anything.
return;
}
tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
// 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 != kPrincipalList) {
nsContainerFrame::SetInitialChildList(aListID, aChildList);
return;
}
MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(),
"unexpected second call to SetInitialChildList");
// 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 eStyleChange? It currently doesn't need
// to, but it might given more optimization.
PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange, 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
{
NS_ASSERTION(!GetPrevInFlow(), "GetColFrame called on next in flow");
int32_t numCols = mColFrames.Length();
if ((aColIndex >= 0) && (aColIndex < numCols)) {
return mColFrames.ElementAt(aColIndex);
}
else {
NS_ERROR("invalid col index");
return nullptr;
}
}
int32_t
nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex,
const nsTableCellFrame& aCell) const
{
nsTableCellMap* cellMap = GetCellMap();
NS_PRECONDITION (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;
OrderRowGroups(rowGroups);
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;
OrderRowGroups(rowGroups);
nsTHashtable<nsPtrHashKey<nsTableRowGroupFrame> > excludeRowGroups;
nsFrameList::Enumerator excludeRowGroupsEnumerator(aRowGroupsToExclude);
while (!excludeRowGroupsEnumerator.AtEnd()) {
excludeRowGroups.PutEntry(static_cast<nsTableRowGroupFrame*>(excludeRowGroupsEnumerator.get()));
#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 =
excludeRowGroupsEnumerator.get()->PrincipalChildList();
for (nsFrameList::Enumerator rows(rowFrames); !rows.AtEnd(); rows.Next()) {
nsTableRowFrame* row = static_cast<nsTableRowFrame*>(rows.get());
MOZ_ASSERT(row->GetRowIndex() == 0,
"exclusions cannot be used for rows that were already added,"
"because we'd need to process mDeletedRowIndexRanges");
}
}
#endif
excludeRowGroupsEnumerator.Next();
}
int32_t rowIndex = 0;
for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
if (!excludeRowGroups.GetEntry(rgFrame)) {
const nsFrameList& rowFrames = rgFrame->PrincipalChildList();
for (nsFrameList::Enumerator rows(rowFrames); !rows.AtEnd(); rows.Next()) {
if (mozilla::StyleDisplay::TableRow ==
rows.get()->StyleDisplay()->mDisplay) {
nsTableRowFrame* row = static_cast<nsTableRowFrame*>(rows.get());
row->SetRowIndex(rowIndex);
rowIndex++;
}
}
}
}
}
void
nsTableFrame::InsertColGroups(int32_t aStartColIndex,
const nsFrameList::Slice& aColGroups)
{
int32_t colIndex = aStartColIndex;
nsFrameList::Enumerator colGroups(aColGroups);
for (; !colGroups.AtEnd(); colGroups.Next()) {
MOZ_ASSERT(colGroups.get()->IsTableColGroupFrame());
nsTableColGroupFrame* cgFrame =
static_cast<nsTableColGroupFrame*>(colGroups.get());
cgFrame->SetStartColumnIndex(colIndex);
// XXXbz this sucks. AddColsToTable will actually remove colgroups from
// the list we're traversing! Need to fix things here. :( I guess this is
// why the old code used pointer-to-last-frame as opposed to
// pointer-to-frame-after-last....
// How about dealing with this by storing a const reference to the
// mNextSibling of the framelist's last frame, instead of storing a pointer
// to the first-after-next frame? Will involve making nsFrameList friend
// of nsIFrame, but it's time for that anyway.
cgFrame->AddColsToTable(colIndex, false,
colGroups.get()->PrincipalChildList());
int32_t numCols = cgFrame->GetColCount();
colIndex += numCols;
}
nsFrameList::Enumerator remainingColgroups = colGroups.GetUnlimitedEnumerator();
if (!remainingColgroups.AtEnd()) {
nsTableColGroupFrame::ResetColIndices(
static_cast<nsTableColGroupFrame*>(remainingColgroups.get()), 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.RemoveElementAt(numCacheCols - 1);
// remove the col from the synthetic col group
nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild();
if (lastColGroup) {
MOZ_ASSERT(lastColGroup->IsSynthetic());
lastColGroup->RemoveChild(*lastCol, false);
// remove the col group if it is empty
if (lastColGroup->GetColCount() <= 0) {
mColGroups.DestroyFrame((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, 1, 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;
}
nsTableColGroupFrame*
nsTableFrame::CreateSyntheticColGroupFrame()
{
nsIContent* colGroupContent = GetContent();
nsPresContext* presContext = PresContext();
nsIPresShell *shell = presContext->PresShell();
RefPtr<nsStyleContext> colGroupStyle;
colGroupStyle = shell->StyleSet()->
ResolveNonInheritingAnonymousBoxStyle(nsCSSAnonBoxes::tableColGroup);
// Create a col group frame
nsTableColGroupFrame* newFrame =
NS_NewTableColGroupFrame(shell, 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)
{
NS_PRECONDITION(aColGroupFrame, "null frame");
NS_PRECONDITION(aColType != eColAnonymousCol, "Shouldn't happen");
MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");
nsIPresShell *shell = 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++) {
nsIContent* iContent;
RefPtr<nsStyleContext> styleContext;
// all anonymous cols that we create here use a pseudo style context of the
// col group
iContent = aColGroupFrame->GetContent();
styleContext = shell->StyleSet()->
ResolveNonInheritingAnonymousBoxStyle(nsCSSAnonBoxes::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(shell, styleContext);
((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, 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()
{
NS_PRECONDITION(!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;
for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) {
nsTableColFrame* colFrame = GetColFrame(colIdx);
if (colFrame && (eColAnonymousCell == colFrame->GetColType())) {
nsTableColGroupFrame* cgFrame =
static_cast<nsTableColGroupFrame*>(colFrame->GetParent());
// remove the frame from the colgroup
cgFrame->RemoveChild(*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(nsTableRowGroupFrame* aRowGroupFrame)
{
RowGroupArray orderedRowGroups;
OrderRowGroups(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(mFrames, 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)
{
NS_PRECONDITION(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;
OrderRowGroups(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 OrderRowGroups,
// 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 (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd();
rowgroups.Next()) {
if (orderedRowGroups[rgIndex] == rowgroups.get()) {
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 (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd();
rowgroups.Next()) {
if (orderedRowGroups[rgIndex] == rowgroups.get()) {
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(rowgroups.get(), 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 == kColGroupList) {
return mColGroups;
}
return nsContainerFrame::GetChildList(aListID);
}
void
nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const
{
nsContainerFrame::GetChildLists(aLists);
mColGroups.AppendIfNonempty(aLists, kColGroupList);
}
nsRect
nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) const
{
*aSnap = false;
return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
}
void
nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame)
{
nsStyleContext *bgSC;
if (!nsCSSRendering::FindBackground(aFrame, &bgSC))
return;
if (!bgSC->StyleBackground()->HasFixedBackground(aFrame))
return;
mPartHasFixedBackground = true;
}
nsDisplayItemGeometry*
nsDisplayTableItem::AllocateGeometry(nsDisplayListBuilder* aBuilder)
{
return new nsDisplayTableItemGeometry(this, aBuilder,
mFrame->GetOffsetTo(mFrame->PresShell()->GetRootFrame()));
}
void
nsDisplayTableItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
const nsDisplayItemGeometry* aGeometry,
nsRegion *aInvalidRegion) const
{
auto geometry =
static_cast<const nsDisplayTableItemGeometry*>(aGeometry);
bool invalidateForAttachmentFixed = false;
if (mDrawsBackground && mPartHasFixedBackground) {
nsPoint frameOffsetToViewport = mFrame->GetOffsetTo(
mFrame->PresShell()->GetRootFrame());
invalidateForAttachmentFixed =
frameOffsetToViewport != geometry->mFrameOffsetToViewport;
}
if (invalidateForAttachmentFixed ||
(aBuilder->ShouldSyncDecodeImages() &&
geometry->ShouldInvalidateToSyncDecodeImages())) {
bool snap;
aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
}
nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
}
// A display item that draws all collapsed borders for a table.
// At some point, we may want to find a nicer partitioning for dividing
// border-collapse segments into their own display items.
class nsDisplayTableBorderCollapse : public nsDisplayTableItem {
public:
nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder,
nsTableFrame* aFrame)
: nsDisplayTableItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse);
}
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayTableBorderCollapse() {
MOZ_COUNT_DTOR(nsDisplayTableBorderCollapse);
}
#endif
virtual void Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) override;
virtual already_AddRefed<layers::Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aContainerParameters) override;
virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
wr::IpcResourceUpdateQueue& aResources,
const StackingContextHelper& aSc,
mozilla::layers::WebRenderLayerManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) override;
virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aParameters) override;
NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE)
};
void
nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx)
{
nsPoint pt = ToReferenceFrame();
DrawTarget* drawTarget = aCtx->GetDrawTarget();
gfxPoint devPixelOffset =
nsLayoutUtils::PointToGfxPoint(pt, mFrame->PresContext()->AppUnitsPerDevPixel());
// XXX we should probably get rid of this translation at some stage
// But that would mean modifying PaintBCBorders, ugh
AutoRestoreTransform autoRestoreTransform(drawTarget);
drawTarget->SetTransform(
drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
static_cast<nsTableFrame*>(mFrame)->PaintBCBorders(*drawTarget, mVisibleRect - pt);
}
already_AddRefed<layers::Layer>
nsDisplayTableBorderCollapse::BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aContainerParameters)
{
return BuildDisplayItemLayer(aBuilder, aManager, aContainerParameters);
}
bool
nsDisplayTableBorderCollapse::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
wr::IpcResourceUpdateQueue& aResources,
const StackingContextHelper& aSc,
mozilla::layers::WebRenderLayerManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder)
{
static_cast<nsTableFrame *>(mFrame)->CreateWebRenderCommandsForBCBorders(aBuilder,
aSc,
ToReferenceFrame());
return true;
}
LayerState
nsDisplayTableBorderCollapse::GetLayerState(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
const ContainerLayerParameters& aParameters)
{
if (gfxPrefs::LayersAllowTable()) {
return LAYER_ACTIVE;
}
return LAYER_NONE;
}
/* static */ void
nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
const nsDisplayListSet& aLists)
{
// This is similar to what nsContainerFrame::BuildDisplayListForNonBlockChildren
// does, except that we allow the children's background and borders to go
// in our BorderBackground list. This doesn't really affect background
// painting --- the children won't actually draw their own backgrounds
// 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* kid : aFrame->GetChildList(kColGroupList)) {
aFrame->BuildDisplayListForChild(aBuilder, kid, aLists);
}
for (nsIFrame* kid : aFrame->PrincipalChildList()) {
aFrame->BuildDisplayListForChild(aBuilder, kid, aLists);
}
}
static void
PaintRowBackground(nsTableRowFrame* aRow,
nsIFrame* aFrame,
nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists,
const nsPoint& aOffset = nsPoint())
{
// Compute background rect by iterating all cell frame.
for (nsTableCellFrame* cell = aRow->GetFirstCell(); cell; cell = cell->GetNextCell()) {
if (!cell->ShouldPaintBackground(aBuilder)) {
continue;
}
auto cellRect = cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset;
if (!aBuilder->GetDirtyRect().Intersects(cellRect)) {
continue;
}
nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect,
aLists.BorderBackground(),
true, nullptr,
aFrame->GetRectRelativeToSelf(),
cell);
}
}
static void
PaintRowGroupBackground(nsTableRowGroupFrame* aRowGroup,
nsIFrame* aFrame,
nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists)
{
for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) {
if (!aBuilder->GetDirtyRect().Intersects(nsRect(row->GetNormalPosition(),
row->GetSize()))) {
continue;
}
PaintRowBackground(row, aFrame, aBuilder, aLists, row->GetNormalPosition());
}
}
static void
PaintRowGroupBackgroundByColIdx(nsTableRowGroupFrame* aRowGroup,
nsIFrame* aFrame,
nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists,
const nsTArray<uint32_t>& aColIdx,
const nsPoint& aOffset)
{
MOZ_DIAGNOSTIC_ASSERT(!aColIdx.IsEmpty(),
"Must be painting backgrounds for something");
for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) {
auto rowPos = row->GetNormalPosition() + aOffset;
if (!aBuilder->GetDirtyRect().Intersects(nsRect(rowPos, row->GetSize()))) {
continue;
}
for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) {
uint32_t curColIdx = cell->ColIndex();
if (!aColIdx.ContainsSorted(curColIdx)) {
if (curColIdx > aColIdx.LastElement()) {
// We can just stop looking at this row.
break;
}
continue;
}
if (!cell->ShouldPaintBackground(aBuilder)) {
continue;
}
auto cellPos = cell->GetNormalPosition() + rowPos;
auto cellRect = nsRect(cellPos, cell->GetSize());
if (!aBuilder->GetDirtyRect().Intersects(cellRect)) {
continue;
}
nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect,
aLists.BorderBackground(),
false, nullptr,
aFrame->GetRectRelativeToSelf(),
cell);
}
}
}
static inline bool FrameHasBorder(nsIFrame* f)
{
if (!f->StyleVisibility()->IsVisible()) {
return false;
}
if (f->StyleBorder()->HasBorder()) {
return true;
}
return false;
}
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(kColGroupList)) {
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;
OrderRowGroups(rowGroups);
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);
}
/* static */ void
nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder,
nsFrame* aFrame,
const nsDisplayListSet& aLists,
DisplayGenericTablePartTraversal aTraversal)
{
bool isVisible = aFrame->IsVisibleForPainting(aBuilder);
bool isTable = aFrame->IsTableFrame();
// Note that we UpdateForFrameBackground() even if we're not visible, unless
// we're a table frame, because our backgrounds may paint anyway if the _cell_
// is visible.
if (isVisible || !isTable) {
nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem();
// currentItem may be null, when none of the table parts have a
// background or border
if (currentItem) {
currentItem->UpdateForFrameBackground(aFrame);
}
}
if (isVisible) {
// XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
// just because we're visible? Or should it depend on the cell visibility
// when we're not the whole table?
// Paint the outset box-shadows for the table frames
if (aFrame->StyleEffects()->mBoxShadow) {
aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame));
}
}
// Background visibility for rows, rowgroups, columns, colgroups depends on
// the visibility of the _cell_, not of the row/col(group).
if (aFrame->IsTableRowGroupFrame()) {
nsTableRowGroupFrame* rowGroup = static_cast<nsTableRowGroupFrame*>(aFrame);
PaintRowGroupBackground(rowGroup, aFrame, aBuilder, aLists);
} else if (aFrame->IsTableRowFrame()) {
nsTableRowFrame* row = static_cast<nsTableRowFrame*>(aFrame);
PaintRowBackground(row, aFrame, aBuilder, aLists);
} else if (aFrame->IsTableColGroupFrame()) {
// Compute background rect by iterating all cell frame.
nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(aFrame);
// Collecting column index.
AutoTArray<uint32_t, 1> colIdx;
for (nsTableColFrame* col = colGroup->GetFirstColumn(); col; col = col->GetNextCol()) {
MOZ_ASSERT(colIdx.IsEmpty() ||
static_cast<uint32_t>(col->GetColIndex()) > colIdx.LastElement());
colIdx.AppendElement(col->GetColIndex());
}
if (!colIdx.IsEmpty()) {
// We have some actual cells that live inside this rowgroup.
nsTableFrame* table = colGroup->GetTableFrame();
RowGroupArray rowGroups;
table->OrderRowGroups(rowGroups);
for (nsTableRowGroupFrame* rowGroup : rowGroups) {
auto offset = rowGroup->GetNormalPosition() - colGroup->GetNormalPosition();
if (!aBuilder->GetDirtyRect().Intersects(nsRect(offset, rowGroup->GetSize()))) {
continue;
}
PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, colIdx, offset);
}
}
} else if (aFrame->IsTableColFrame()) {
// Compute background rect by iterating all cell frame.
nsTableColFrame* col = static_cast<nsTableColFrame*>(aFrame);
AutoTArray<uint32_t, 1> colIdx;
colIdx.AppendElement(col->GetColIndex());
nsTableFrame* table = col->GetTableFrame();
RowGroupArray rowGroups;
table->OrderRowGroups(rowGroups);
for (nsTableRowGroupFrame* rowGroup : rowGroups) {
auto offset = rowGroup->GetNormalPosition() -
col->GetNormalPosition() -
col->GetTableColGroupFrame()->GetNormalPosition();
if (!aBuilder->GetDirtyRect().Intersects(nsRect(offset, rowGroup->GetSize()))) {
continue;
}
PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, colIdx, offset);
}
} else if (isVisible) {
nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame,
aFrame->GetRectRelativeToSelf(),
aLists.BorderBackground());
}
if (isVisible) {
// XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted
// just because we're visible? Or should it depend on the cell visibility
// when we're not the whole table?
// Paint the inset box-shadows for the table frames
if (aFrame->StyleEffects()->mBoxShadow) {
aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayBoxShadowInner(aBuilder, aFrame));
}
}
aFrame->DisplayOutline(aBuilder, aLists);
aTraversal(aBuilder, aFrame, aLists);
if (isVisible) {
if (isTable) {
nsTableFrame* table = static_cast<nsTableFrame*>(aFrame);
// In the collapsed border model, overlay all collapsed borders.
if (table->IsBorderCollapse()) {
if (table->HasBCBorders()) {
aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayTableBorderCollapse(aBuilder, table));
}
} else {
const nsStyleBorder* borderStyle = aFrame->StyleBorder();
if (borderStyle->HasBorder()) {
aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayBorder(aBuilder, table));
}
}
}
}
}
// 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));
DisplayGenericTablePart(aBuilder, this, aLists);
}
nsMargin
nsTableFrame::GetDeflationForBackground(nsPresContext* aPresContext) const
{
if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() ||
!IsBorderCollapse())
return nsMargin(0,0,0,0);
WritingMode wm = GetWritingMode();
return GetOuterBCBorder(wm).GetPhysicalMargin(wm);
}
nsIFrame::LogicalSides
nsTableFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
{
if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Clone)) {
return LogicalSides();
}
LogicalSides skip;
// frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto
// account for pagination
if (nullptr != GetPrevInFlow()) {
skip |= eLogicalSideBitsBStart;
}
if (nullptr != GetNextInFlow()) {
skip |= eLogicalSideBitsBEnd;
}
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 cellSpacingI = 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");
cellSpacingI = GetColSpacing(colIdx);
colGroupISize += fif->GetColumnISizeFromFirstInFlow(colIdx) +
cellSpacingI;
++colIdx;
}
}
if (colGroupISize) {
colGroupISize -= cellSpacingI;
}
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);
cellSpacingI = GetColSpacing(colIdx);
colOrigin.I(aWM) += colISize + cellSpacingI;
++colIdx;
}
}
colGroupOrigin.I(aWM) += colGroupISize + cellSpacingI;
}
}
// 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
nsTableFrame::RowGroupArray rowGroups;
OrderRowGroups(rowGroups);
// 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::IntrinsicISizeOffsetData
nsTableFrame::IntrinsicISizeOffsets()
{
IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets();
result.hMargin = 0;
result.hPctMargin = 0;
if (IsBorderCollapse()) {
result.hPadding = 0;
result.hPctPadding = 0;
WritingMode wm = GetWritingMode();
LogicalMargin outerBC = GetIncludedOuterBCBorder(wm);
result.hBorder = outerBC.IStartEnd(wm);
}
return result;
}
/* virtual */
LogicalSize
nsTableFrame::ComputeSize(gfxContext* aRenderingContext,
WritingMode aWM,
const LogicalSize& aCBSize,
nscoord aAvailableISize,
const LogicalSize& aMargin,
const LogicalSize& aBorder,
const LogicalSize& aPadding,
ComputeSizeFlags aFlags)
{
LogicalSize result =
nsContainerFrame::ComputeSize(aRenderingContext, aWM,
aCBSize, aAvailableISize,
aMargin, aBorder, aPadding, aFlags);
// XXX The code below doesn't make sense if the caller's writing mode
// is orthogonal to this frame's. Not sure yet what should happen then;
// for now, just bail out.
if (aWM.IsVertical() != GetWritingMode().IsVertical()) {
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.ISize(aWM)) {
result.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 nsFrame::ShrinkWidthToFit 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& aBorder,
const LogicalSize& aPadding,
ComputeSizeFlags aFlags)
{
// Tables always shrink-wrap.
nscoord cbBased = aAvailableISize - aMargin.ISize(aWM) - aBorder.ISize(aWM) -
aPadding.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 (IS_TABLE_CELL(frameType) ||
(LayoutFrameType::TableRow == frameType) ||
(LayoutFrameType::TableRowGroup == frameType)) {
const nsStyleCoord &bsize = rs->mStylePosition->BSize(wm);
// calc() with percentages treated like 'auto' on internal table elements
if (bsize.GetUnit() != eStyleUnit_Auto &&
(!bsize.IsCalcUnit() || !bsize.HasPercent())) {
return true;
}
} else if (LayoutFrameType::Table == frameType) {
// we reached the containing table, so always return
return rs->mStylePosition->BSize(wm).GetUnit() != eStyleUnit_Auto;
}
}
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(IS_TABLE_CELL(aReflowInput.mFrame->Type()) ||
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()) &&
eStyleUnit_Percent == aReflowInput.mStylePosition->BSize(wm).GetUnit() && // 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(IS_TABLE_CELL(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 state'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 state'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");
DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
bool isPaginated = aPresContext->IsPaginated();
WritingMode wm = aReflowInput.GetWritingMode();
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();
}
aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
// Check for an overflow list, and append any row group frames being pushed
MoveOverflowToChildList();
bool haveDesiredBSize = false;
SetHaveReflowedColGroups(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.
bool fixupKidPositions = false;
if (NS_SUBTREE_DIRTY(this) ||
aReflowInput.ShouldReflowAllKids() ||
IsGeometryDirty() ||
isPaginated ||
aReflowInput.IsBResize()) {
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);
if ((tableSpecifiedBSize > 0) &&
(tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE)) {
needToInitiateSpecialReflow = true;
}
}
} else {
needToInitiateSpecialReflow =
HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
}
nsIFrame* lastChildReflowed = nullptr;
NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow,
"Shouldn't be in special bsize reflow here!");
// do the pass 2 reflow unless this is a special bsize reflow and we will be
// initiating a special bsize reflow
// XXXldb I changed this. Should I change it back?
// if we need to initiate a special bsize reflow, then don't constrain the
// bsize of the reflow before that
nscoord availBSize = needToInitiateSpecialReflow
? NS_UNCONSTRAINEDSIZE
: aReflowInput.AvailableBSize();
ReflowTable(aDesiredSize, aReflowInput, availBSize,
lastChildReflowed, aStatus);
// If ComputedWidth is unconstrained, we may need to fix child positions
// later (in vertical-rl mode) due to use of 0 as a dummy
// containerSize.width during ReflowChildren.
fixupKidPositions = wm.IsVerticalRL() &&
aReflowInput.ComputedWidth() == NS_UNCONSTRAINEDSIZE;
// 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 states?
ReflowInput &mutable_rs =
const_cast<ReflowInput&>(aReflowInput);
// distribute extra block-direction space to rows
CalcDesiredBSize(aReflowInput, aDesiredSize);
mutable_rs.mFlags.mSpecialBSizeReflow = true;
ReflowTable(aDesiredSize, aReflowInput, aReflowInput.AvailableBSize(),
lastChildReflowed, aStatus);
if (lastChildReflowed && aStatus.IsIncomplete()) {
// if there is an incomplete child, then set the desired bsize
// to include it but not the next one
LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
aDesiredSize.BSize(wm) =
borderPadding.BEnd(wm) + GetRowSpacing(GetRowCount()) +
lastChildReflowed->GetNormalRect().YMost(); // XXX YMost should be B-flavored
}
haveDesiredBSize = true;
mutable_rs.mFlags.mSpecialBSizeReflow = false;
}
}
aDesiredSize.ISize(wm) = aReflowInput.ComputedISize() +
aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
if (!haveDesiredBSize) {
CalcDesiredBSize(aReflowInput, aDesiredSize);
}
if (IsRowInserted()) {
ProcessRowInserted(aDesiredSize.BSize(wm));
}
if (fixupKidPositions) {
// If we didn't already know the containerSize (and so used zero during
// ReflowChildren), then we need to update the block-position of our kids.
for (nsIFrame* kid : mFrames) {
kid->MovePositionBy(nsPoint(aDesiredSize.Width(), 0));
RePositionViews(kid);
}
}
// Calculate the overflow area contribution from our children. We couldn't
// do this on the fly during ReflowChildren(), because in vertical-rl mode
// with unconstrained width, we weren't placing them in their final positions
// until the fixupKidPositions loop just above.
for (nsIFrame* kid : mFrames) {
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid);
}
LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding,
aDesiredSize.PhysicalSize());
if (NeedToCollapse() &&
(NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize())) {
AdjustForCollapsingRowsCols(aDesiredSize, wm, borderPadding);
}
// If there are any relatively-positioned table parts, we need to reflow their
// absolutely-positioned descendants now that their dimensions are final.
FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowInput);
// make sure the table overflow area does include the table rect.
nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()) ;
if (!ShouldApplyOverflowClipping(this, aReflowInput.mStyleDisplay)) {
// collapsed border may leak out
LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm);
tableRect.Inflate(bcMargin.GetPhysicalMargin(wm));
}
aDesiredSize.mOverflowAreas.UnionAllWith(tableRect);
if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
nsIFrame::InvalidateFrame();
}
FinishAndStoreOverflow(&aDesiredSize);
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
}
void
nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput)
{
FrameTArray* positionedParts = GetProperty(PositionedTablePartArray());
if (!positionedParts) {
return;
}
OverflowChangedTracker overflowTracker;
overflowTracker.SetSubtreeRoot(this);
for (size_t i = 0; i < positionedParts->Length(); ++i) {
nsIFrame* positionedPart = positionedParts->ElementAt(i);
// As we've already finished reflow, positionedParts's size and overflow
// areas have already been assigned, so we just pull them back out.
nsSize size(positionedPart->GetSize());
ReflowOutput desiredSize(aReflowInput.GetWritingMode());
desiredSize.Width() = size.width;
desiredSize.Height() = size.height;
desiredSize.mOverflowAreas = positionedPart->GetOverflowAreasRelativeToSelf();
// Construct a dummy reflow state and reflow status.
// XXX(seth): Note that the dummy reflow state doesn't have a correct
// chain of parent reflow states. It also doesn't necessarily have a
// correct containing block.
WritingMode wm = positionedPart->GetWritingMode();
LogicalSize availSize(wm, size);
availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
ReflowInput reflowInput(aPresContext, positionedPart,
aReflowInput.mRenderingContext, availSize,
ReflowInput::DUMMY_PARENT_REFLOW_STATE);
nsReflowStatus reflowStatus;
// Reflow absolutely-positioned descendants of the positioned part.
// FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the bsize and
// ignoring any change to the reflow status aren't correct. We'll never
// paginate absolutely positioned frames.
nsFrame* positionedFrame = static_cast<nsFrame*>(positionedPart);
positionedFrame->FinishReflowWithAbsoluteFrames(PresContext(),
desiredSize,
reflowInput,
reflowStatus,
true);
// FinishReflowWithAbsoluteFrames has updated overflow on
// |positionedPart|. We need to make sure that update propagates
// through the intermediate frames between it and this frame.
nsIFrame* positionedFrameParent = positionedPart->GetParent();
if (positionedFrameParent != this) {
overflowTracker.AddFrame(positionedFrameParent,
OverflowChangedTracker::CHILDREN_CHANGED);
}
}
// Propagate updated overflow areas up the tree.
overflowTracker.Flush();
// Update our own overflow areas. (OverflowChangedTracker doesn't update the
// subtree root itself.)
aDesiredSize.SetOverflowAreasToDesiredBounds();
nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas);
}
bool
nsTableFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
{
// As above in Reflow, make sure the table overflow area includes the table
// rect, and check for collapsed borders leaking out.
if (!ShouldApplyOverflowClipping(this, StyleDisplay())) {
nsRect bounds(nsPoint(0, 0), GetSize());
WritingMode wm = GetWritingMode();
LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm);
bounds.Inflate(bcMargin.GetPhysicalMargin(wm));
aOverflowAreas.UnionAllWith(bounds);
}
return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
}
void
nsTableFrame::ReflowTable(ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nscoord aAvailBSize,
nsIFrame*& aLastChildReflowed,
nsReflowStatus& aStatus)
{
aLastChildReflowed = nullptr;
if (!GetPrevInFlow()) {
mTableLayoutStrategy->ComputeColumnISizes(aReflowInput);
}
// Constrain our reflow isize to the computed table isize (of the 1st in flow).
// and our reflow bsize to our avail bsize minus border, padding, cellspacing
WritingMode wm = aReflowInput.GetWritingMode();
aDesiredSize.ISize(wm) = aReflowInput.ComputedISize() +
aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
TableReflowInput reflowInput(aReflowInput,
LogicalSize(wm, aDesiredSize.ISize(wm),
aAvailBSize));
ReflowChildren(reflowInput, aStatus, aLastChildReflowed,
aDesiredSize.mOverflowAreas);
ReflowColGroups(aReflowInput.mRenderingContext);
}
nsIFrame*
nsTableFrame::GetFirstBodyRowGroupFrame()
{
nsIFrame* headerFrame = nullptr;
nsIFrame* footerFrame = nullptr;
for (nsIFrame* kidFrame : mFrames) {
const nsStyleDisplay* childDisplay = kidFrame->StyleDisplay();
// We expect the header and footer row group frames to be first, and we only
// allow one header and one footer
if (mozilla::StyleDisplay::TableHeaderGroup == childDisplay->mDisplay) {
if (headerFrame) {
// We already have a header frame and so this header frame is treated
// like an ordinary body row group frame
return kidFrame;
}
headerFrame = kidFrame;
} else if (mozilla::StyleDisplay::TableFooterGroup == childDisplay->mDisplay) {
if (footerFrame) {
// We already have a footer frame and so this footer frame is treated
// like an ordinary body row group frame
return kidFrame;
}
footerFrame = kidFrame;
} else if (mozilla::StyleDisplay::TableRowGroup == childDisplay->mDisplay) {
return kidFrame;
}
}
return nullptr;
}
// Table specific version that takes into account repeated header and footer
// frames when continuing table frames
void
nsTableFrame::PushChildren(const RowGroupArray& aRowGroups,
int32_t aPushFrom)
{
NS_PRECONDITION(aPushFrom > 0, "pushing first child");
// extract the frames from the array into a sibling list
nsFrameList frames;
uint32_t childX;
for (childX = aPushFrom; childX < aRowGroups.Length(); ++childX) {
nsTableRowGroupFrame* rgFrame = aRowGroups[childX];
if (!rgFrame->IsRepeatable()) {
mFrames.RemoveFrame(rgFrame);
frames.AppendFrame(nullptr, rgFrame);
}
}
if (frames.IsEmpty()) {
return;
}
nsTableFrame* nextInFlow = static_cast<nsTableFrame*>(GetNextInFlow());
if (nextInFlow) {
// Insert the frames after any repeated header and footer frames.
nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame();
nsIFrame* prevSibling = nullptr;
if (firstBodyFrame) {
prevSibling = firstBodyFrame->GetPrevSibling();
}
// When pushing and pulling frames we need to check for whether any
// views need to be reparented.
ReparentFrameViewList(frames, this, nextInFlow);
nextInFlow->mFrames.InsertFrames(nextInFlow, prevSibling,
frames);
}
else {
// Add the frames to our overflow list.
SetOverflowFrames(frames);
}
}
// collapsing row groups, rows, col groups and cols are accounted for after both passes of
// reflow so that it has no effect on the calculations of reflow.
void
nsTableFrame::AdjustForCollapsingRowsCols(ReflowOutput& aDesiredSize,
const WritingMode aWM,
const LogicalMargin& aBorderPadding)
{
nscoord bTotalOffset = 0; // total offset among all rows in all row groups
// reset the bit, it will be set again if row/rowgroup or col/colgroup are
// collapsed
SetNeedToCollapse(false);
// collapse the rows and/or row groups as necessary
// Get the ordered children
RowGroupArray rowGroups;
OrderRowGroups(rowGroups);
nsTableFrame* firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding);
nscoord rgISize = iSize - GetColSpacing(-1) -
GetColSpacing(GetColCount());
nsOverflowAreas overflow;
// Walk the list of children
for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) {
nsTableRowGroupFrame* rgFrame = rowGroups[childX];
NS_ASSERTION(rgFrame, "Must have row group frame here");
bTotalOffset += rgFrame->CollapseRowGroupIfNecessary(bTotalOffset, rgISize,
aWM);
ConsiderChildOverflow(overflow, rgFrame);
}
aDesiredSize.BSize(aWM) -= bTotalOffset;
aDesiredSize.ISize(aWM) = iSize;
overflow.UnionAllWith(nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()));
FinishAndStoreOverflow(overflow,
nsSize(aDesiredSize.Width(), aDesiredSize.Height()));
}
nscoord
nsTableFrame::GetCollapsedISize(const WritingMode aWM,
const LogicalMargin& aBorderPadding)
{
NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedISize called on next in flow");
nscoord iSize = GetColSpacing(GetColCount());
iSize += aBorderPadding.IStartEnd(aWM);
nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
for (nsIFrame* groupFrame : mColGroups) {
const nsStyleVisibility* groupVis = groupFrame->StyleVisibility();
bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible);
nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame;
for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame;
colFrame = colFrame->GetNextCol()) {
const nsStyleDisplay* colDisplay = colFrame->StyleDisplay();
nscoord colIdx = colFrame->GetColIndex();
if (mozilla::StyleDisplay::TableColumn == colDisplay->mDisplay) {
const nsStyleVisibility* colVis = colFrame->StyleVisibility();
bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible);
nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
if (!collapseGroup && !collapseCol) {
iSize += colISize;
if (ColumnHasCellSpacingBefore(colIdx)) {
iSize += GetColSpacing(colIdx - 1);
}
}
else {
SetNeedToCollapse(true);
}
}
}
}
return iSize;
}
/* virtual */ void
nsTableFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
{
nsContainerFrame::DidSetStyleContext(aOldStyleContext);
if (!aOldStyleContext) //avoid this on init
return;
if (IsBorderCollapse() &&
BCRecalcNeeded(aOldStyleContext, StyleContext())) {
SetFullBCDamageArea();
}
//avoid this on init or nextinflow
if (!mTableLayoutStrategy || GetPrevInFlow())
return;
bool isAuto = IsAutoLayout();
if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) {
nsITableLayoutStrategy* temp;
if (isAuto)
temp = new BasicTableLayoutStrategy(this);
else
temp = new FixedTableLayoutStrategy(this);
if (temp) {
delete mTableLayoutStrategy;
mTableLayoutStrategy = temp;
}
}
}
void
nsTableFrame::AppendFrames(ChildListID aListID,
nsFrameList& aFrameList)
{
NS_ASSERTION(aListID == kPrincipalList || aListID == kColGroupList,
"unexpected child list");
// Because we actually have two child lists, one for col group frames and one
// for everything else, we need to look at each frame individually
// XXX The frame construction code should be separating out child frames
// based on the type, bug 343048.
while (!aFrameList.IsEmpty()) {
nsIFrame* f = aFrameList.FirstChild();
aFrameList.RemoveFrame(f);
// See what kind of frame we have
const nsStyleDisplay* display = f->StyleDisplay();
if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
if (MOZ_UNLIKELY(GetPrevInFlow())) {
nsFrameList colgroupFrame(f, f);
auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
firstInFlow->AppendFrames(aListID, colgroupFrame);
continue;
}
nsTableColGroupFrame* lastColGroup =
nsTableColGroupFrame::GetLastRealColGroup(this);
int32_t startColIndex = (lastColGroup)
? lastColGroup->GetStartColumnIndex() + lastColGroup->GetColCount() : 0;
mColGroups.InsertFrame(this, lastColGroup, f);
// Insert the colgroup and its cols into the table
InsertColGroups(startColIndex,
nsFrameList::Slice(mColGroups, f, f->GetNextSibling()));
} else if (IsRowGroup(display->mDisplay)) {
DrainSelfOverflowList(); // ensure the last frame is in mFrames
// Append the new row group frame to the sibling chain
mFrames.AppendFrame(nullptr, f);
// insert the row group and its rows into the table
InsertRowGroups(nsFrameList::Slice(mFrames, f, nullptr));
} else {
// Nothing special to do, just add the frame to our child list
NS_NOTREACHED("How did we get here? Frame construction screwed up");
mFrames.AppendFrame(nullptr, f);
}
}
#ifdef DEBUG_TABLE_CELLMAP
printf("=== TableFrame::AppendFrames\n");
Dump(true, true, true);
#endif
PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN);
SetGeometryDirty();
}
// Needs to be at file scope or ArrayLength fails to compile.
struct ChildListInsertions {
nsIFrame::ChildListID mID;
nsFrameList mList;
};
void
nsTableFrame::InsertFrames(ChildListID aListID,
nsIFrame* aPrevFrame,
nsFrameList& aFrameList)
{
// The frames in aFrameList can be a mix of row group frames and col group
// frames. The problem is that they should go in separate child lists so
// we need to deal with that here...
// XXX The frame construction code should be separating out child frames
// based on the type, bug 343048.
NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
"inserting after sibling frame with different parent");
if ((aPrevFrame && !aPrevFrame->GetNextSibling()) ||
(!aPrevFrame && GetChildList(aListID).IsEmpty())) {
// Treat this like an append; still a workaround for bug 343048.
AppendFrames(aListID, aFrameList);
return;
}
// Collect ColGroupFrames into a separate list and insert those separately
// from the other frames (bug 759249).
ChildListInsertions insertions[2]; // ColGroup, other
const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay();
nsFrameList::FrameLinkEnumerator e(aFrameList);
for (; !aFrameList.IsEmpty(); e.Next()) {
nsIFrame* next = e.NextFrame();
if (!next || next->StyleDisplay()->mDisplay != display->mDisplay) {
nsFrameList head = aFrameList.ExtractHead(e);
if (display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) {
insertions[0].mID = kColGroupList;
insertions[0].mList.AppendFrames(nullptr, head);
} else {
insertions[1].mID = kPrincipalList;
insertions[1].mList.AppendFrames(nullptr, head);
}
if (!next) {
break;
}
display = next->StyleDisplay();
}
}
for (uint32_t i = 0; i < ArrayLength(insertions); ++i) {
// We pass aPrevFrame for both ColGroup and other frames since
// HomogenousInsertFrames will only use it if it's a suitable
// prev-sibling for the frames in the frame list.
if (!insertions[i].mList.IsEmpty()) {
HomogenousInsertFrames(insertions[i].mID, aPrevFrame,
insertions[i].mList);
}
}
}
void
nsTableFrame::HomogenousInsertFrames(ChildListID aListID,
nsIFrame* aPrevFrame,
nsFrameList& aFrameList)
{
// See what kind of frame we have
const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay();
bool isColGroup = mozilla::StyleDisplay::TableColumnGroup == display->mDisplay;
#ifdef DEBUG
// Verify that either all siblings have display:table-column-group, or they
// all have display values different from table-column-group.
for (nsIFrame* frame : aFrameList) {
auto nextDisplay = frame->StyleDisplay()->mDisplay;
MOZ_ASSERT(isColGroup ==
(nextDisplay == mozilla::StyleDisplay::TableColumnGroup),
"heterogenous childlist");
}
#endif
if (MOZ_UNLIKELY(isColGroup && GetPrevInFlow())) {
auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
firstInFlow->AppendFrames(aListID, aFrameList);
return;
}
if (aPrevFrame) {
const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay();
// Make sure they belong on the same frame list
if ((display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) !=
(prevDisplay->mDisplay == mozilla::StyleDisplay::TableColumnGroup)) {
// the previous frame is not valid, see comment at ::AppendFrames
// XXXbz Using content indices here means XBL will get screwed
// over... Oh, well.
nsIFrame* pseudoFrame = aFrameList.FirstChild();
nsIContent* parentContent = GetContent();
nsIContent* content = nullptr;
aPrevFrame = nullptr;
while (pseudoFrame && (parentContent ==
(content = pseudoFrame->GetContent()))) {
pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
}
nsCOMPtr<nsIContent> container = content->GetParent();
if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823.
int32_t newIndex = container->IndexOf(content);
nsIFrame* kidFrame;
nsTableColGroupFrame* lastColGroup = nullptr;
if (isColGroup) {
kidFrame = mColGroups.FirstChild();
lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this);
}
else {
kidFrame = mFrames.FirstChild();
}
// Important: need to start at a value smaller than all valid indices
int32_t lastIndex = -1;
while (kidFrame) {
if (isColGroup) {
if (kidFrame == lastColGroup) {
aPrevFrame = kidFrame; // there is no real colgroup after this one
break;
}
}
pseudoFrame = kidFrame;
while (pseudoFrame && (parentContent ==
(content = pseudoFrame->GetContent()))) {
pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
}
int32_t index = container->IndexOf(content);
if (index > lastIndex && index < newIndex) {
lastIndex = index;
aPrevFrame = kidFrame;
}
kidFrame = kidFrame->GetNextSibling();
}
}
}
}
if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
NS_ASSERTION(aListID == kColGroupList, "unexpected child list");
// Insert the column group frames
const nsFrameList::Slice& newColgroups =
mColGroups.InsertFrames(this, aPrevFrame, aFrameList);
// find the starting col index for the first new col group
int32_t startColIndex = 0;
if (aPrevFrame) {
nsTableColGroupFrame* prevColGroup =
(nsTableColGroupFrame*)GetFrameAtOrBefore(this, aPrevFrame,
LayoutFrameType::TableColGroup);
if (prevColGroup) {
startColIndex = prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount();
}
}
InsertColGroups(startColIndex, newColgroups);
} else if (IsRowGroup(display->mDisplay)) {
NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
// Insert the frames in the sibling chain
const nsFrameList::Slice& newRowGroups =
mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
InsertRowGroups(newRowGroups);
} else {
NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
NS_NOTREACHED("How did we even get here?");
// Just insert the frame and don't worry about reflowing it
mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
return;
}
PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN);
SetGeometryDirty();
#ifdef DEBUG_TABLE_CELLMAP
printf("=== TableFrame::InsertFrames\n");
Dump(true, true, true);
#endif
}
void
nsTableFrame::DoRemoveFrame(ChildListID aListID,
nsIFrame* aOldFrame)
{
if (aListID == kColGroupList) {
nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling();
nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame;
int32_t firstColIndex = colGroup->GetStartColumnIndex();
int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1;
mColGroups.DestroyFrame(aOldFrame);
nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex);
// remove the cols from the table
int32_t colIdx;
for (colIdx = lastColIndex; colIdx >= firstColIndex; colIdx--) {
nsTableColFrame* colFrame = mColFrames.SafeElementAt(colIdx);
if (colFrame) {
RemoveCol(colGroup, colIdx, true, false);
}
}
// If we have some anonymous cols at the end already, we just
// add more of them.
if (!mColFrames.IsEmpty() &&
mColFrames.LastElement() && // XXXbz is this ever null?
mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length();
if (numAnonymousColsToAdd > 0) {
// this sets the child list, updates the col cache and cell map
AppendAnonymousColFrames(numAnonymousColsToAdd);
}
} 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.
nsTableCellMap* cellMap = GetCellMap();
if (cellMap) { // XXXbz is this ever null?
cellMap->RemoveColsAtEnd();
MatchCellMapToColCache(cellMap);
}
}
} else {
NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
nsTableRowGroupFrame* rgFrame =
static_cast<nsTableRowGroupFrame*>(aOldFrame);
// remove the row group from the cell map
nsTableCellMap* cellMap = GetCellMap();
if (cellMap) {
cellMap->RemoveGroupCellMap(rgFrame);
}
// remove the row group frame from the sibling chain
mFrames.DestroyFrame(aOldFrame);
// the removal of a row group changes the cellmap, the columns might change
if (cellMap) {
cellMap->Synchronize(this);
// Create an empty slice
ResetRowIndices(nsFrameList::Slice(mFrames, nullptr, nullptr));
TableArea damageArea;
cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, damageArea);
static_cast<nsTableFrame*>(FirstInFlow())->MatchCellMapToColCache(cellMap);
}
}
}
void
nsTableFrame::RemoveFrame(ChildListID aListID,
nsIFrame* aOldFrame)
{
NS_ASSERTION(aListID == kColGroupList ||
mozilla::StyleDisplay::TableColumnGroup !=
aOldFrame->StyleDisplay()->mDisplay,
"Wrong list name; use kColGroupList iff colgroup");
nsIPresShell* shell = PresShell();
nsTableFrame* lastParent = nullptr;
while (aOldFrame) {
nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation();
nsTableFrame* parent = static_cast<nsTableFrame*>(aOldFrame->GetParent());
if (parent != lastParent) {
parent->DrainSelfOverflowList();
}
parent->DoRemoveFrame(aListID, aOldFrame);
aOldFrame = oldFrameNextContinuation;
if (parent != lastParent) {
// for now, just bail and recalc all of the collapsing borders
// as the cellmap changes we need to recalc
if (parent->IsBorderCollapse()) {
parent->SetFullBCDamageArea();
}
parent->SetGeometryDirty();
shell->FrameNeedsReflow(parent, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN);
lastParent = parent;
}
}
#ifdef DEBUG_TABLE_CELLMAP
printf("=== TableFrame::RemoveFrame\n");
Dump(true, true, true);
#endif
}
/* virtual */ nsMargin
nsTableFrame::GetUsedBorder() const
{
if (!IsBorderCollapse())
return nsContainerFrame::GetUsedBorder();
WritingMode wm = GetWritingMode();
return GetIncludedOuterBCBorder(wm).GetPhysicalMargin(wm);
}
/* virtual */ nsMargin
nsTableFrame::GetUsedPadding() const
{
if (!IsBorderCollapse())
return nsContainerFrame::GetUsedPadding();
return nsMargin(0,0,0,0);
}
/* virtual */ nsMargin
nsTableFrame::GetUsedMargin() const
{
// The margin is inherited to the table wrapper frame via
// the ::-moz-table-wrapper rule in ua.css.
return nsMargin(0, 0, 0, 0);
}
NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCProperty, BCPropertyData)
BCPropertyData*
nsTableFrame::GetBCProperty() const
{
return GetProperty(TableBCProperty());
}
BCPropertyData*
nsTableFrame::GetOrCreateBCProperty()
{
BCPropertyData* value = GetProperty(TableBCProperty());
if (!value) {
value = new BCPropertyData();
SetProperty(TableBCProperty(), value);
}
return value;
}
static void
DivideBCBorderSize(BCPixelSize aPixelSize,
BCPixelSize& aSmallHalf,
BCPixelSize& aLargeHalf)
{
aSmallHalf = aPixelSize / 2;
aLargeHalf = aPixelSize - aSmallHalf;
}
LogicalMargin
nsTableFrame::GetOuterBCBorder(const WritingMode aWM) const
{
if (NeedToCalcBCBorders()) {
const_cast<nsTableFrame*>(this)->CalcBCBorders();
}
int32_t d2a = PresContext()->AppUnitsPerDevPixel();
BCPropertyData* propData = GetBCProperty();
if (propData) {
return LogicalMargin(aWM,
BC_BORDER_START_HALF_COORD(d2a, propData->mBStartBorderWidth),
BC_BORDER_END_HALF_COORD(d2a, propData->mIEndBorderWidth),
BC_BORDER_END_HALF_COORD(d2a, propData->mBEndBorderWidth),
BC_BORDER_START_HALF_COORD(d2a, propData->mIStartBorderWidth));
}
return LogicalMargin(aWM);
}
LogicalMargin
nsTableFrame::GetIncludedOuterBCBorder(const WritingMode aWM) const
{
if (NeedToCalcBCBorders()) {
const_cast<nsTableFrame*>(this)->CalcBCBorders();
}
int32_t d2a = PresContext()->AppUnitsPerDevPixel();
BCPropertyData* propData = GetBCProperty();
if (propData) {
return LogicalMargin(aWM,
BC_BORDER_START_HALF_COORD(d2a, propData->mBStartBorderWidth),
BC_BORDER_END_HALF_COORD(d2a, propData->mIEndCellBorderWidth),
BC_BORDER_END_HALF_COORD(d2a, propData->mBEndBorderWidth),
BC_BORDER_START_HALF_COORD(d2a, propData->mIStartCellBorderWidth));
}
return LogicalMargin(aWM);
}
LogicalMargin
nsTableFrame::GetExcludedOuterBCBorder(const WritingMode aWM) const
{
return GetOuterBCBorder(aWM) - GetIncludedOuterBCBorder(aWM);
}
static LogicalMargin
GetSeparateModelBorderPadding(const WritingMode aWM,
const ReflowInput* aReflowInput,
nsStyleContext* aStyleContext)
{
// XXXbz Either we _do_ have a reflow state and then we can use its
// mComputedBorderPadding or we don't and then we get the padding
// wrong!
const nsStyleBorder* border = aStyleContext->StyleBorder();
LogicalMargin borderPadding(aWM, border->GetComputedBorder());
if (aReflowInput) {
borderPadding += aReflowInput->ComputedLogicalPadding();
}
return borderPadding;
}
LogicalMargin
nsTableFrame::GetChildAreaOffset(const WritingMode aWM,
const ReflowInput* aReflowInput) const
{
return IsBorderCollapse() ? GetIncludedOuterBCBorder(aWM) :
GetSeparateModelBorderPadding(aWM, aReflowInput, mStyleContext);
}
void
nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput)
{
nsMargin collapseBorder;
nsMargin padding(0,0,0,0);
nsMargin* pCollapseBorder = nullptr;
nsPresContext* presContext = PresContext();
if (IsBorderCollapse()) {
nsTableRowGroupFrame* rgFrame =
static_cast<nsTableRowGroupFrame*>(aReflowInput.mFrame);
WritingMode wm = GetWritingMode();
LogicalMargin border = rgFrame->GetBCBorderWidth(wm);
collapseBorder = border.GetPhysicalMargin(wm);
pCollapseBorder = &collapseBorder;
}
aReflowInput.Init(presContext, nullptr, pCollapseBorder, &padding);
NS_ASSERTION(!mBits.mResizedColumns ||
!aReflowInput.mParentReflowInput->mFlags.mSpecialBSizeReflow,
"should not resize columns on special bsize reflow");
if (mBits.mResizedColumns) {
aReflowInput.SetIResize(true);
}
}
// Position and size aKidFrame and update our reflow state. The origin of
// aKidRect is relative to the upper-left origin of our frame
void
nsTableFrame::PlaceChild(TableReflowInput& aReflowInput,
nsIFrame* aKidFrame,
nsPoint aKidPosition,
ReflowOutput& aKidDesiredSize,
const nsRect& aOriginalKidRect,
const nsRect& aOriginalKidVisualOverflow)
{
WritingMode wm = aReflowInput.reflowInput.GetWritingMode();
bool isFirstReflow =
aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
// Place and size the child
FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr,
aKidPosition.x, aKidPosition.y, 0);
InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow,
isFirstReflow);
// Adjust the running block-offset
aReflowInput.bCoord += aKidDesiredSize.BSize(wm);
// If our bsize is constrained, then update the available bsize
if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
aReflowInput.availSize.BSize(wm) -= aKidDesiredSize.BSize(wm);
}
}
void
nsTableFrame::OrderRowGroups(RowGroupArray& aChildren,
nsTableRowGroupFrame** aHead,
nsTableRowGroupFrame** aFoot) const
{
aChildren.Clear();
nsTableRowGroupFrame* head = nullptr;
nsTableRowGroupFrame* foot = nullptr;
nsIFrame* kidFrame = mFrames.FirstChild();
while (kidFrame) {
const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay();
nsTableRowGroupFrame* rowGroup =
static_cast<nsTableRowGroupFrame*>(kidFrame);
switch (kidDisplay->mDisplay) {
case mozilla::StyleDisplay::TableHeaderGroup:
if (head) { // treat additional thead like tbody
aChildren.AppendElement(rowGroup);
}
else {
head = rowGroup;
}
break;
case mozilla::StyleDisplay::TableFooterGroup:
if (foot) { // treat additional tfoot like tbody
aChildren.AppendElement(rowGroup);
}
else {
foot = rowGroup;
}
break;
case mozilla::StyleDisplay::TableRowGroup:
aChildren.AppendElement(rowGroup);
break;
default:
NS_NOTREACHED("How did this produce an nsTableRowGroupFrame?");
// Just ignore it
break;
}
// Get the next sibling but skip it if it's also the next-in-flow, since
// a next-in-flow will not be part of the current table.
while (kidFrame) {
nsIFrame* nif = kidFrame->GetNextInFlow();
kidFrame = kidFrame->GetNextSibling();
if (kidFrame != nif)
break;
}
}
// put the thead first
if (head) {
aChildren.InsertElementAt(0, head);
}
if (aHead)
*aHead = head;
// put the tfoot after the last tbody
if (foot) {
aChildren.AppendElement(foot);
}
if (aFoot)
*aFoot = foot;
}
nsTableRowGroupFrame*
nsTableFrame::GetTHead() const
{
nsIFrame* kidFrame = mFrames.FirstChild();
while (kidFrame) {
if (kidFrame->StyleDisplay()->mDisplay ==
mozilla::StyleDisplay::TableHeaderGroup) {
return static_cast<nsTableRowGroupFrame*>(kidFrame);
}
// Get the next sibling but skip it if it's also the next-in-flow, since
// a next-in-flow will not be part of the current table.
while (kidFrame) {
nsIFrame* nif = kidFrame->GetNextInFlow();
kidFrame = kidFrame->GetNextSibling();
if (kidFrame != nif)
break;
}
}
return nullptr;
}
nsTableRowGroupFrame*
nsTableFrame::GetTFoot() const
{
nsIFrame* kidFrame = mFrames.FirstChild();
while (kidFrame) {
if (kidFrame->StyleDisplay()->mDisplay ==
mozilla::StyleDisplay::TableFooterGroup) {
return static_cast<nsTableRowGroupFrame*>(kidFrame);
}
// Get the next sibling but skip it if it's also the next-in-flow, since
// a next-in-flow will not be part of the current table.
while (kidFrame) {
nsIFrame* nif = kidFrame->GetNextInFlow();
kidFrame = kidFrame->GetNextSibling();
if (kidFrame != nif)
break;
}
}
return nullptr;
}
static bool
IsRepeatable(nscoord aFrameHeight, nscoord aPageHeight)
{
return aFrameHeight < (aPageHeight / 4);
}
nsresult
nsTableFrame::SetupHeaderFooterChild(const TableReflowInput& aReflowInput,
nsTableRowGroupFrame* aFrame,
nscoord* aDesiredHeight)
{
nsPresContext* presContext = PresContext();
nscoord pageHeight = presContext->GetPageSize().height;
// Reflow the child with unconstrained height
WritingMode wm = aFrame->GetWritingMode();
LogicalSize availSize = aReflowInput.reflowInput.AvailableSize(wm);
nsSize containerSize = availSize.GetPhysicalSize(wm);
// XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE
availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
ReflowInput kidReflowInput(presContext, aReflowInput.reflowInput,
aFrame, availSize, nullptr,
ReflowInput::CALLER_WILL_INIT);
InitChildReflowInput(kidReflowInput);
kidReflowInput.mFlags.mIsTopOfPage = true;
ReflowOutput desiredSize(aReflowInput.reflowInput);
desiredSize.ClearSize();
nsReflowStatus status;
ReflowChild(aFrame, presContext, desiredSize, kidReflowInput,
wm, LogicalPoint(wm, aReflowInput.iCoord, aReflowInput.bCoord),
containerSize, 0, status);
// The child will be reflowed again "for real" so no need to place it now
aFrame->SetRepeatable(IsRepeatable(desiredSize.Height(), pageHeight));
*aDesiredHeight = desiredSize.Height();
return NS_OK;
}
void
nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput,
nsTableRowGroupFrame *aTfoot,
nscoord aFooterHeight)
{
nsPresContext* presContext = PresContext();
WritingMode wm = aTfoot->GetWritingMode();
LogicalSize kidAvailSize = aReflowInput.availSize;
nsSize containerSize = kidAvailSize.GetPhysicalSize(wm);
// XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE
kidAvailSize.BSize(wm) = aFooterHeight;
ReflowInput footerReflowInput(presContext,
aReflowInput.reflowInput,
aTfoot, kidAvailSize,
nullptr,
ReflowInput::CALLER_WILL_INIT);
InitChildReflowInput(footerReflowInput);
aReflowInput.bCoord += GetRowSpacing(GetRowCount());
nsRect origTfootRect = aTfoot->GetRect();
nsRect origTfootVisualOverflow = aTfoot->GetVisualOverflowRect();
nsReflowStatus footerStatus;
ReflowOutput desiredSize(aReflowInput.reflowInput);
desiredSize.ClearSize();
LogicalPoint kidPosition(wm, aReflowInput.iCoord, aReflowInput.bCoord);
ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput,
wm, kidPosition, containerSize, 0, footerStatus);
footerReflowInput.ApplyRelativePositioning(&kidPosition, containerSize);
PlaceChild(aReflowInput, aTfoot,
// We subtract desiredSize.PhysicalSize() from containerSize here
// to account for the fact that in RTL modes, the origin is
// on the right-hand side so we're not simply converting a
// point, we're also swapping the child's origin side.
kidPosition.GetPhysicalPoint(wm, containerSize -
desiredSize.PhysicalSize()),
desiredSize, origTfootRect, origTfootVisualOverflow);
}
// Reflow the children based on the avail size and reason in aReflowInput
void
nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput,
nsReflowStatus& aStatus,
nsIFrame*& aLastChildReflowed,
nsOverflowAreas& aOverflowAreas)
{
aStatus.Reset();
aLastChildReflowed = nullptr;
nsIFrame* prevKidFrame = nullptr;
WritingMode wm = aReflowInput.reflowInput.GetWritingMode();
NS_WARNING_ASSERTION(
wm.IsVertical() ||
NS_UNCONSTRAINEDSIZE != aReflowInput.reflowInput.ComputedWidth(),
"shouldn't have unconstrained width in horizontal mode");
nsSize containerSize =
aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained();
nsPresContext* presContext = PresContext();
// XXXldb Should we be checking constrained height instead?
// tables are not able to pull back children from its next inflow, so even
// under paginated contexts tables are should not paginate if they are inside
// column set
bool isPaginated = presContext->IsPaginated() &&
NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm) &&
aReflowInput.reflowInput.mFlags.mTableIsSplittable;
// Tables currently (though we ought to fix this) only fragment in
// paginated contexts, not in multicolumn contexts. (See bug 888257.)
// This is partly because they don't correctly handle incremental
// layout when paginated.
//
// Since we propagate NS_FRAME_IS_DIRTY from parent to child at the
// start of the parent's reflow (behavior that's new as of bug
// 1308876), we can do things that are effectively incremental reflow
// during paginated layout. Since the table code doesn't handle this
// correctly, we need to set the flag that says to reflow everything
// within the table structure.
if (presContext->IsPaginated()) {
SetGeometryDirty();
}
aOverflowAreas.Clear();
bool reflowAllKids = aReflowInput.reflowInput.ShouldReflowAllKids() ||
mBits.mResizedColumns ||
IsGeometryDirty();
RowGroupArray rowGroups;
nsTableRowGroupFrame *thead, *tfoot;
OrderRowGroups(rowGroups, &thead, &tfoot);
bool pageBreak = false;
nscoord footerHeight = 0;
// Determine the repeatablility of headers and footers, and also the desired
// height of any repeatable footer.
// The repeatability of headers on continued tables is handled
// when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame.
// We handle the repeatability of footers again here because we need to
// determine the footer's height anyway. We could perhaps optimize by
// using the footer's prev-in-flow's height instead of reflowing it again,
// but there's no real need.
if (isPaginated) {
if (thead && !GetPrevInFlow()) {
nscoord desiredHeight;
nsresult rv = SetupHeaderFooterChild(aReflowInput, thead, &desiredHeight);
if (NS_FAILED(rv))
return;
}
if (tfoot) {
nsresult rv = SetupHeaderFooterChild(aReflowInput, tfoot, &footerHeight);
if (NS_FAILED(rv))
return;
}
}
// if the child is a tbody in paginated mode reduce the height by a repeated footer
bool allowRepeatedFooter = false;
for (size_t childX = 0; childX < rowGroups.Length(); childX++) {
nsIFrame* kidFrame = rowGroups[childX];
nsTableRowGroupFrame* rowGroupFrame = rowGroups[childX];
nscoord cellSpacingB = GetRowSpacing(rowGroupFrame->GetStartRowIndex()+
rowGroupFrame->GetRowCount());
// Get the frame state bits
// See if we should only reflow the dirty child frames
if (reflowAllKids ||
NS_SUBTREE_DIRTY(kidFrame) ||
(aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow &&
(isPaginated || kidFrame->HasAnyStateBits(
NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
if (pageBreak) {
if (allowRepeatedFooter) {
PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
}
else if (tfoot && tfoot->IsRepeatable()) {
tfoot->SetRepeatable(false);
}
PushChildren(rowGroups, childX);
aStatus.Reset();
aStatus.SetIncomplete();
break;
}
LogicalSize kidAvailSize(aReflowInput.availSize);
allowRepeatedFooter = false;
if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) {
nsTableRowGroupFrame* kidRG =
static_cast<nsTableRowGroupFrame*>(kidFrame);
if (kidRG != thead && kidRG != tfoot && tfoot && tfoot->IsRepeatable()) {
// the child is a tbody and there is a repeatable footer
NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1], "Missing footer!");
if (footerHeight + cellSpacingB < kidAvailSize.BSize(wm)) {
allowRepeatedFooter = true;
kidAvailSize.BSize(wm) -= footerHeight + cellSpacingB;
}
}
}
nsRect oldKidRect = kidFrame->GetRect();
nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect();
ReflowOutput desiredSize(aReflowInput.reflowInput);
desiredSize.ClearSize();
// Reflow the child into the available space
ReflowInput kidReflowInput(presContext, aReflowInput.reflowInput,
kidFrame,
kidAvailSize,
nullptr,
ReflowInput::CALLER_WILL_INIT);
InitChildReflowInput(kidReflowInput);
// If this isn't the first row group, and the previous row group has a
// nonzero YMost, then we can't be at the top of the page.
// We ignore a repeated head row group in this check to avoid causing
// infinite loops in some circumstances - see bug 344883.
if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) &&
(rowGroups[childX - 1]->GetNormalRect().YMost() > 0)) {
kidReflowInput.mFlags.mIsTopOfPage = false;
}
aReflowInput.bCoord += cellSpacingB;
if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
aReflowInput.availSize.BSize(wm) -= cellSpacingB;
}
// record the presence of a next in flow, it might get destroyed so we
// need to reorder the row group array
bool reorder = false;
if (kidFrame->GetNextInFlow())
reorder = true;
LogicalPoint kidPosition(wm, aReflowInput.iCoord, aReflowInput.bCoord);
aStatus.Reset();
ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput,
wm, kidPosition, containerSize, 0, aStatus);
kidReflowInput.ApplyRelativePositioning(&kidPosition, containerSize);
if (reorder) {
// reorder row groups the reflow may have changed the nextinflows
OrderRowGroups(rowGroups, &thead, &tfoot);
childX = rowGroups.IndexOf(kidFrame);
if (childX == RowGroupArray::NoIndex) {
// XXXbz can this happen?
childX = rowGroups.Length();
}
}
if (isPaginated && !aStatus.IsFullyComplete() &&
ShouldAvoidBreakInside(aReflowInput.reflowInput)) {
aStatus.SetInlineLineBreakBeforeAndReset();
break;
}
// see if the rowgroup did not fit on this page might be pushed on
// the next page
if (isPaginated &&
(aStatus.IsInlineBreakBefore() ||
(aStatus.IsComplete() &&
(NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight()) &&
kidReflowInput.AvailableHeight() < desiredSize.Height()))) {
if (ShouldAvoidBreakInside(aReflowInput.reflowInput)) {
aStatus.SetInlineLineBreakBeforeAndReset();
break;
}
// if we are on top of the page place with dataloss
if (kidReflowInput.mFlags.mIsTopOfPage) {
if (childX+1 < rowGroups.Length()) {
nsIFrame* nextRowGroupFrame = rowGroups[childX + 1];
if (nextRowGroupFrame) {
PlaceChild(aReflowInput, kidFrame,
kidPosition.GetPhysicalPoint(wm,
containerSize - desiredSize.PhysicalSize()),
desiredSize, oldKidRect, oldKidVisualOverflow);
if (allowRepeatedFooter) {
PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
}
else if (tfoot && tfoot->IsRepeatable()) {
tfoot->SetRepeatable(false);
}
aStatus.Reset();
aStatus.SetIncomplete();
PushChildren(rowGroups, childX + 1);
aLastChildReflowed = kidFrame;
break;
}
}
}
else { // we are not on top, push this rowgroup onto the next page
if (prevKidFrame) { // we had a rowgroup before so push this
if (allowRepeatedFooter) {
PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
}
else if (tfoot && tfoot->IsRepeatable()) {
tfoot->SetRepeatable(false);
}
aStatus.Reset();
aStatus.SetIncomplete();
PushChildren(rowGroups, childX);
aLastChildReflowed = prevKidFrame;
break;
}
else { // we can't push so lets make clear how much space we need
PlaceChild(aReflowInput, kidFrame,
kidPosition.GetPhysicalPoint(wm,
containerSize - desiredSize.PhysicalSize()),
desiredSize, oldKidRect, oldKidVisualOverflow);
aLastChildReflowed = kidFrame;
if (allowRepeatedFooter) {
PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
aLastChildReflowed = tfoot;
}
break;
}
}
}
aLastChildReflowed = kidFrame;
pageBreak = false;
// see if there is a page break after this row group or before the next one
if (aStatus.IsComplete() && isPaginated &&
(NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight())) {
nsIFrame* nextKid =
(childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr;
pageBreak = PageBreakAfter(kidFrame, nextKid);
}
// Place the child
PlaceChild(aReflowInput, kidFrame,
kidPosition.GetPhysicalPoint(wm, containerSize -
desiredSize.PhysicalSize()),
desiredSize, oldKidRect, oldKidVisualOverflow);
// Remember where we just were in case we end up pushing children
prevKidFrame = kidFrame;
MOZ_ASSERT(!aStatus.IsIncomplete() || isPaginated,
"Table contents should only fragment in paginated contexts");
// Special handling for incomplete children
if (isPaginated && aStatus.IsIncomplete()) {
nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();
if (!kidNextInFlow) {
// The child doesn't have a next-in-flow so create a continuing
// frame. This hooks the child into the flow
kidNextInFlow = presContext->PresShell()->FrameConstructor()->
CreateContinuingFrame(presContext, kidFrame, this);
// Insert the kid's new next-in-flow into our sibling list...
mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow);
// and in rowGroups after childX so that it will get pushed below.
rowGroups.InsertElementAt(childX + 1,
static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
} else if (kidNextInFlow == kidFrame->GetNextSibling()) {
// OrderRowGroups excludes NIFs in the child list from 'rowGroups'
// so we deal with that here to make sure they get pushed.
MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow),
"OrderRowGroups must not put our NIF in 'rowGroups'");
rowGroups.InsertElementAt(childX + 1,
static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
}
// We've used up all of our available space so push the remaining
// children.
if (allowRepeatedFooter) {
PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
}
else if (tfoot && tfoot->IsRepeatable()) {
tfoot->SetRepeatable(false);
}
nsIFrame* nextSibling = kidFrame->GetNextSibling();
if (nextSibling) {
PushChildren(rowGroups, childX + 1);
}
break;
}
}
else { // it isn't being reflowed
aReflowInput.bCoord += cellSpacingB;
LogicalRect kidRect(wm, kidFrame->GetNormalRect(), containerSize);
if (kidRect.BStart(wm) != aReflowInput.bCoord) {
// invalidate the old position
kidFrame->InvalidateFrameSubtree();
// move to the new position
kidFrame->MovePositionBy(wm, LogicalPoint(wm, 0, aReflowInput.bCoord -
kidRect.BStart(wm)));
RePositionViews(kidFrame);
// invalidate the new position
kidFrame->InvalidateFrameSubtree();
}
aReflowInput.bCoord += kidRect.BSize(wm);
// If our bsize is constrained then update the available bsize.
if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
aReflowInput.availSize.BSize(wm) -= cellSpacingB + kidRect.BSize(wm);
}
}
}
// We've now propagated the column resizes and geometry changes to all
// the children.
mBits.mResizedColumns = false;
ClearGeometryDirty();
}
void
nsTableFrame::ReflowColGroups(gfxContext *aRenderingContext)
{
if (!GetPrevInFlow() && !HaveReflowedColGroups()) {
ReflowOutput kidMet(GetWritingMode());
nsPresContext *presContext = PresContext();
for (nsIFrame* kidFrame : mColGroups) {
if (NS_SUBTREE_DIRTY(kidFrame)) {
// The column groups don't care about dimensions or reflow states.
ReflowInput
kidReflowInput(presContext, kidFrame, aRenderingContext,
LogicalSize(kidFrame->GetWritingMode()));
nsReflowStatus cgStatus;
ReflowChild(kidFrame, presContext, kidMet, kidReflowInput, 0, 0, 0,
cgStatus);
FinishReflowChild(kidFrame, presContext, kidMet, nullptr, 0, 0, 0);
}
}
SetHaveReflowedColGroups(true);
}
}
void
nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput,
ReflowOutput& aDesiredSize)
{
WritingMode wm = aReflowInput.GetWritingMode();
nsTableCellMap* cellMap = GetCellMap();
if (!cellMap) {
NS_ERROR("never ever call me until the cell map is built!");
aDesiredSize.BSize(wm) = 0;
return;
}
LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
// get the natural bsize based on the last child's (row group) rect
RowGroupArray rowGroups;
OrderRowGroups(rowGroups);
if (rowGroups.IsEmpty()) {
// tables can be used as rectangular items without content
nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput);
if ((NS_UNCONSTRAINEDSIZE != tableSpecifiedBSize) &&
(tableSpecifiedBSize > 0) &&
eCompatibility_NavQuirks != PresContext()->CompatibilityMode()) {
// empty tables should not have a size in quirks mode
aDesiredSize.BSize(wm) = tableSpecifiedBSize;
} else {
aDesiredSize.BSize(wm) = 0;
}
return;
}
int32_t rowCount = cellMap->GetRowCount();
int32_t colCount = cellMap->GetColCount();
nscoord desiredBSize = borderPadding.BStartEnd(wm);
if (rowCount > 0 && colCount > 0) {
desiredBSize += GetRowSpacing(-1);
for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
desiredBSize += rowGroups[rgIdx]->BSize(wm) +
GetRowSpacing(rowGroups[rgIdx]->GetRowCount() +
rowGroups[rgIdx]->GetStartRowIndex());
}
}
// see if a specified table bsize requires dividing additional space to rows
if (!GetPrevInFlow()) {
nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput);
if ((tableSpecifiedBSize > 0) &&
(tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE) &&
(tableSpecifiedBSize > desiredBSize)) {
// proportionately distribute the excess bsize to unconstrained rows in each
// unconstrained row group.
DistributeBSizeToRows(aReflowInput, tableSpecifiedBSize - desiredBSize);
// this might have changed the overflow area incorporate the childframe overflow area.
for (nsIFrame* kidFrame : mFrames) {
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
}
desiredBSize = tableSpecifiedBSize;
}
}
aDesiredSize.BSize(wm) = desiredBSize;
}
static
void ResizeCells(nsTableFrame& aTableFrame)
{
nsTableFrame::RowGroupArray rowGroups;
aTableFrame.OrderRowGroups(rowGroups);
WritingMode wm = aTableFrame.GetWritingMode();
ReflowOutput tableDesiredSize(wm);
tableDesiredSize.SetSize(wm, aTableFrame.GetLogicalSize(wm));
tableDesiredSize.SetOverflowAreasToDesiredBounds();
for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
ReflowOutput groupDesiredSize(wm);
groupDesiredSize.SetSize(wm, rgFrame->GetLogicalSize(wm));
groupDesiredSize.SetOverflowAreasToDesiredBounds();
nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
while (rowFrame) {
rowFrame->DidResize();
rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame);
rowFrame = rowFrame->GetNextRow();
}
rgFrame->FinishAndStoreOverflow(&groupDesiredSize);
tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas +
rgFrame->GetPosition());
}
aTableFrame.FinishAndStoreOverflow(&tableDesiredSize);
}
void
nsTableFrame::DistributeBSizeToRows(const ReflowInput& aReflowInput,
nscoord aAmount)
{
WritingMode wm = aReflowInput.GetWritingMode();
LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
nsSize containerSize =
aReflowInput.ComputedSizeAsContainerIfConstrained();
RowGroupArray rowGroups;
OrderRowGroups(rowGroups);
nscoord amountUsed = 0;
// distribute space to each pct bsize row whose row group doesn't have a computed
// bsize, and base the pct on the table bsize. If the row group had a computed
// bsize, then this was already done in nsTableRowGroupFrame::CalculateRowBSizes
nscoord pctBasis = aReflowInput.ComputedBSize() - GetRowSpacing(-1, GetRowCount());
nscoord bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(0);
nscoord bEndRG = bOriginRG;
uint32_t rgIdx;
for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
nscoord amountUsedByRG = 0;
nscoord bOriginRow = 0;
LogicalRect rgNormalRect(wm, rgFrame->GetNormalRect(), containerSize);
if (!rgFrame->HasStyleBSize()) {
nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
while (rowFrame) {
// We don't know the final width of the rowGroupFrame yet, so use 0,0
// as a dummy containerSize here; we'll adjust the row positions at
// the end, after the rowGroup size is finalized.
const nsSize dummyContainerSize;
LogicalRect rowNormalRect(wm, rowFrame->GetNormalRect(),
dummyContainerSize);
nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex());
if ((amountUsed < aAmount) && rowFrame->HasPctBSize()) {
nscoord pctBSize = rowFrame->GetInitialBSize(pctBasis);
nscoord amountForRow = std::min(aAmount - amountUsed,
pctBSize - rowNormalRect.BSize(wm));
if (amountForRow > 0) {
// XXXbz we don't need to move the row's b-position to bOriginRow?
nsRect origRowRect = rowFrame->GetRect();
nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
rowFrame->SetSize(wm, LogicalSize(wm, rowNormalRect.ISize(wm),
newRowBSize));
bOriginRow += newRowBSize + cellSpacingB;
bEndRG += newRowBSize + cellSpacingB;
amountUsed += amountForRow;
amountUsedByRG += amountForRow;
//rowFrame->DidResize();
nsTableFrame::RePositionViews(rowFrame);
rgFrame->InvalidateFrameWithRect(origRowRect);
rgFrame->InvalidateFrame();
}
}
else {
if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm) &&
!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
rowFrame->InvalidateFrameSubtree();
rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow -
rowNormalRect.BStart(wm)));
nsTableFrame::RePositionViews(rowFrame);
rowFrame->InvalidateFrameSubtree();
}
bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB;
bEndRG += rowNormalRect.BSize(wm) + cellSpacingB;
}
rowFrame = rowFrame->GetNextRow();
}
if (amountUsed > 0) {
if (rgNormalRect.BStart(wm) != bOriginRG) {
rgFrame->InvalidateFrameSubtree();
}
nsRect origRgNormalRect = rgFrame->GetRect();
nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect();
rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
rgNormalRect.BStart(wm)));
rgFrame->SetSize(wm, LogicalSize(wm, rgNormalRect.ISize(wm),
rgNormalRect.BSize(wm) + amountUsedByRG));
nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
origRgVisualOverflow, false);
}
}
else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
rgFrame->InvalidateFrameSubtree();
rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
rgNormalRect.BStart(wm)));
// Make sure child views are properly positioned
nsTableFrame::RePositionViews(rgFrame);
rgFrame->InvalidateFrameSubtree();
}
bOriginRG = bEndRG;
}
if (amountUsed >= aAmount) {
ResizeCells(*this);
return;
}
// get the first row without a style bsize where its row group has an
// unconstrained bsize
nsTableRowGroupFrame* firstUnStyledRG = nullptr;
nsTableRowFrame* firstUnStyledRow = nullptr;
for (rgIdx = 0; rgIdx < rowGroups.Length() && !firstUnStyledRG; rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
if (!rgFrame->HasStyleBSize()) {
nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
while (rowFrame) {
if (!rowFrame->HasStyleBSize()) {
firstUnStyledRG = rgFrame;
firstUnStyledRow = rowFrame;
break;
}
rowFrame = rowFrame->GetNextRow();
}
}
}
nsTableRowFrame* lastEligibleRow = nullptr;
// Accumulate the correct divisor. This will be the total bsize of all
// unstyled rows inside unstyled row groups, unless there are none, in which
// case, it will be number of all rows. If the unstyled rows don't have a
// bsize, divide the space equally among them.
nscoord divisor = 0;
int32_t eligibleRows = 0;
bool expandEmptyRows = false;
if (!firstUnStyledRow) {
// there is no unstyled row
divisor = GetRowCount();
}
else {
for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
if (!firstUnStyledRG || !rgFrame->HasStyleBSize()) {
nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
while (rowFrame) {
if (!firstUnStyledRG || !rowFrame->HasStyleBSize()) {
NS_ASSERTION(rowFrame->BSize(wm) >= 0,
"negative row frame block-size");
divisor += rowFrame->BSize(wm);
eligibleRows++;
lastEligibleRow = rowFrame;
}
rowFrame = rowFrame->GetNextRow();
}
}
}
if (divisor <= 0) {
if (eligibleRows > 0) {
expandEmptyRows = true;
}
else {
NS_ERROR("invalid divisor");
return;
}
}
}
// allocate the extra bsize to the unstyled row groups and rows
nscoord bSizeToDistribute = aAmount - amountUsed;
bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(-1);
bEndRG = bOriginRG;
for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
nscoord amountUsedByRG = 0;
nscoord bOriginRow = 0;
LogicalRect rgNormalRect(wm, rgFrame->GetNormalRect(), containerSize);
nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect();
// see if there is an eligible row group or we distribute to all rows
if (!firstUnStyledRG || !rgFrame->HasStyleBSize() || !eligibleRows) {
for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
rowFrame; rowFrame = rowFrame->GetNextRow()) {
nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex());
// We don't know the final width of the rowGroupFrame yet, so use 0,0
// as a dummy containerSize here; we'll adjust the row positions at
// the end, after the rowGroup size is finalized.
const nsSize dummyContainerSize;
LogicalRect rowNormalRect(wm, rowFrame->GetNormalRect(),
dummyContainerSize);
nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect();
// see if there is an eligible row or we distribute to all rows
if (!firstUnStyledRow || !rowFrame->HasStyleBSize() || !eligibleRows) {
float ratio;
if (eligibleRows) {
if (!expandEmptyRows) {
// The amount of additional space each row gets is proportional
// to its bsize
ratio = float(rowNormalRect.BSize(wm)) / float(divisor);
} else {
// empty rows get all the same additional space
ratio = 1.0f / float(eligibleRows);
}
}
else {
// all rows get the same additional space
ratio = 1.0f / float(divisor);
}
// give rows their additional space, except for the last row which
// gets the remainder
nscoord amountForRow =
(rowFrame == lastEligibleRow)
? aAmount - amountUsed
: NSToCoordRound(((float)(bSizeToDistribute)) * ratio);
amountForRow = std::min(amountForRow, aAmount - amountUsed);
if (bOriginRow != rowNormalRect.BStart(wm)) {
rowFrame->InvalidateFrameSubtree();
}
// update the row bsize
nsRect origRowRect = rowFrame->GetRect();
nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow -
rowNormalRect.BStart(wm)));
rowFrame->SetSize(wm, LogicalSize(wm, rowNormalRect.ISize(wm),
newRowBSize));
bOriginRow += newRowBSize + cellSpacingB;
bEndRG += newRowBSize + cellSpacingB;
amountUsed += amountForRow;
amountUsedByRG += amountForRow;
NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation");
//rowFrame->DidResize();
nsTableFrame::RePositionViews(rowFrame);
nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect,
rowVisualOverflow, false);
}
else {
if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm)) {
rowFrame->InvalidateFrameSubtree();
rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow -
rowNormalRect.BStart(wm)));
nsTableFrame::RePositionViews(rowFrame);
rowFrame->InvalidateFrameSubtree();
}
bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB;
bEndRG += rowNormalRect.BSize(wm) + cellSpacingB;
}
}
if (amountUsed > 0) {
if (rgNormalRect.BStart(wm) != bOriginRG) {
rgFrame->InvalidateFrameSubtree();
}
nsRect origRgNormalRect = rgFrame->GetRect();
rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
rgNormalRect.BStart(wm)));
rgFrame->SetSize(wm, LogicalSize(wm, rgNormalRect.ISize(wm),
rgNormalRect.BSize(wm) + amountUsedByRG));
nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
rgVisualOverflow, false);
}
// For vertical-rl mode, we needed to position the rows relative to the
// right-hand (block-start) side of the group; but we couldn't do that
// above, as we didn't know the rowGroupFrame's final block size yet.
// So we used a dummyContainerSize of 0,0 earlier, placing the rows to
// the left of the rowGroupFrame's (physical) origin. Now we move them
// all rightwards by its final width.
if (wm.IsVerticalRL()) {
nscoord rgWidth = rgFrame->GetSize().width;
for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
rowFrame; rowFrame = rowFrame->GetNextRow()) {
rowFrame->InvalidateFrameSubtree();
rowFrame->MovePositionBy(nsPoint(rgWidth, 0));
nsTableFrame::RePositionViews(rowFrame);
rowFrame->InvalidateFrameSubtree();
}
}
}
else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
rgFrame->InvalidateFrameSubtree();
rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
rgNormalRect.BStart(wm)));
// Make sure child views are properly positioned
nsTableFrame::RePositionViews(rgFrame);
rgFrame->InvalidateFrameSubtree();
}
bOriginRG = bEndRG;
}
ResizeCells(*this);
}
nscoord
nsTableFrame::GetColumnISizeFromFirstInFlow(int32_t aColIndex)
{
MOZ_ASSERT(this == FirstInFlow());
nsTableColFrame* colFrame = GetColFrame(aColIndex);
return colFrame ? colFrame->GetFinalISize() : 0;
}
nscoord
nsTableFrame::GetColSpacing()
{
if (IsBorderCollapse())
return 0;
return StyleTableBorder()->mBorderSpacingCol;
}
// XXX: could cache this. But be sure to check style changes if you do!
nscoord
nsTableFrame::GetColSpacing(int32_t aColIndex)
{
NS_ASSERTION(aColIndex >= -1 && aColIndex <= GetColCount(),
"Column index exceeds the bounds of the table");
// Index is irrelevant for ordinary tables. We check that it falls within
// appropriate bounds to increase confidence of correctness in situations
// where it does matter.
return GetColSpacing();
}
nscoord
nsTableFrame::GetColSpacing(int32_t aStartColIndex,
int32_t aEndColIndex)
{
NS_ASSERTION(aStartColIndex >= -1 && aStartColIndex <= GetColCount(),
"Start column index exceeds the bounds of the table");
NS_ASSERTION(aEndColIndex >= -1 && aEndColIndex <= GetColCount(),
"End column index exceeds the bounds of the table");
NS_ASSERTION(aStartColIndex <= aEndColIndex,
"End index must not be less than start index");
// Only one possible value so just multiply it out. Tables where index
// matters will override this function
return GetColSpacing() * (aEndColIndex - aStartColIndex);
}
nscoord
nsTableFrame::GetRowSpacing()
{
if (IsBorderCollapse())
return 0;
return StyleTableBorder()->mBorderSpacingRow;
}
// XXX: could cache this. But be sure to check style changes if you do!
nscoord
nsTableFrame::GetRowSpacing(int32_t aRowIndex)
{
NS_ASSERTION(aRowIndex >= -1 && aRowIndex <= GetRowCount(),
"Row index exceeds the bounds of the table");
// Index is irrelevant for ordinary tables. We check that it falls within
// appropriate bounds to increase confidence of correctness in situations
// where it does matter.
return GetRowSpacing();
}
nscoord
nsTableFrame::GetRowSpacing(int32_t aStartRowIndex,
int32_t aEndRowIndex)
{
NS_ASSERTION(aStartRowIndex >= -1 && aStartRowIndex <= GetRowCount(),
"Start row index exceeds the bounds of the table");
NS_ASSERTION(aEndRowIndex >= -1 && aEndRowIndex <= GetRowCount(),
"End row index exceeds the bounds of the table");
NS_ASSERTION(aStartRowIndex <= aEndRowIndex,
"End index must not be less than start index");
// Only one possible value so just multiply it out. Tables where index
// matters will override this function
return GetRowSpacing() * (aEndRowIndex - aStartRowIndex);
}
/* virtual */ nscoord
nsTableFrame::GetLogicalBaseline(WritingMode aWM) const
{
nscoord baseline;
if (!GetNaturalBaselineBOffset(aWM, BaselineSharingGroup::eFirst, &baseline)) {
baseline = BSize(aWM);
}
return baseline;
}
/* virtual */ bool
nsTableFrame::GetNaturalBaselineBOffset(WritingMode aWM,
BaselineSharingGroup aBaselineGroup,
nscoord* aBaseline) const
{
RowGroupArray orderedRowGroups;
OrderRowGroups(orderedRowGroups);
// XXX not sure if this should be the size of the containing block instead.
nsSize containerSize = mRect.Size();
auto TableBaseline = [aWM, containerSize] (nsTableRowGroupFrame* aRowGroup,
nsTableRowFrame* aRow) {
nscoord rgBStart = LogicalRect(aWM, aRowGroup->GetNormalRect(),
containerSize).BStart(aWM);
nscoord rowBStart = LogicalRect(aWM, aRow->GetNormalRect(),
containerSize).BStart(aWM);
return rgBStart + rowBStart + aRow->GetRowBaseline(aWM);
};
if (aBaselineGroup == BaselineSharingGroup::eFirst) {
for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
nsTableRowFrame* row = rgFrame->GetFirstRow();
if (row) {
*aBaseline = TableBaseline(rgFrame, row);
return true;
}
}
} else {
for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) {
nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
nsTableRowFrame* row = rgFrame->GetLastRow();
if (row) {
*aBaseline = BSize(aWM) - TableBaseline(rgFrame, row);
return true;
}
}
}
return false;
}
/* ----- global methods ----- */
nsTableFrame*
NS_NewTableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
return new (aPresShell) nsTableFrame(aContext);
}
NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame)
nsTableFrame*
nsTableFrame::GetTableFrame(nsIFrame* aFrame)
{
for (nsIFrame* ancestor = aFrame->GetParent(); ancestor;
ancestor = ancestor->GetParent()) {
if (ancestor->IsTableFrame()) {
return static_cast<nsTableFrame*>(ancestor);
}
}
MOZ_CRASH("unable to find table parent");
return nullptr;
}
nsTableFrame*
nsTableFrame::GetTableFramePassingThrough(nsIFrame* aMustPassThrough,
nsIFrame* aFrame,
bool* aDidPassThrough)
{
MOZ_ASSERT(aMustPassThrough == aFrame ||
nsLayoutUtils::IsProperAncestorFrame(aMustPassThrough, aFrame),
"aMustPassThrough should be an ancestor");
// Retrieve the table frame, and check if we hit aMustPassThrough on the
// way.
*aDidPassThrough = false;
nsTableFrame* tableFrame = nullptr;
for (nsIFrame* ancestor = aFrame; ancestor; ancestor = ancestor->GetParent()) {
if (ancestor == aMustPassThrough) {
*aDidPassThrough = true;
}
if (ancestor->IsTableFrame()) {
tableFrame = static_cast<nsTableFrame*>(ancestor);
break;
}
}
MOZ_ASSERT(tableFrame, "Should have a table frame here");
return tableFrame;
}
bool
nsTableFrame::IsAutoBSize(WritingMode aWM)
{
const nsStyleCoord &bsize = StylePosition()->BSize(aWM);
// Don't consider calc() here like this quirk for percent.
return bsize.GetUnit() == eStyleUnit_Auto ||
(bsize.GetUnit() == eStyleUnit_Percent &&
bsize.GetPercentValue() <= 0.0f);
}
nscoord
nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aReflowInput)
{
nscoord bSize = aReflowInput.ComputedBSize();
if (NS_AUTOHEIGHT != bSize) {
WritingMode wm = aReflowInput.GetWritingMode();
LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
bSize += borderPadding.BStartEnd(wm);
}
bSize = std::max(0, bSize);
return bSize;
}
bool
nsTableFrame::IsAutoLayout()
{
if (StyleTable()->mLayoutStrategy == NS_STYLE_TABLE_LAYOUT_AUTO)
return true;
// a fixed-layout inline-table must have a inline size
// and tables with inline size set to '-moz-max-content' must be
// auto-layout (at least as long as
// FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX)
const nsStyleCoord &iSize = StylePosition()->ISize(GetWritingMode());
return (iSize.GetUnit() == eStyleUnit_Auto) ||
(iSize.GetUnit() == eStyleUnit_Enumerated &&
iSize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT);
}
#ifdef DEBUG_FRAME_DUMP
nsresult
nsTableFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("Table"), aResult);
}
#endif
// Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) that
// is of type aChildType
nsIFrame*
nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame,
nsIFrame* aPriorChildFrame,
LayoutFrameType aChildType)
{
nsIFrame* result = nullptr;
if (!aPriorChildFrame) {
return result;
}
if (aChildType == aPriorChildFrame->Type()) {
return aPriorChildFrame;
}
// aPriorChildFrame is not of type aChildType, so we need start from
// the beginnng and find the closest one
nsIFrame* lastMatchingFrame = nullptr;
nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild();
while (childFrame && (childFrame != aPriorChildFrame)) {
if (aChildType == childFrame->Type()) {
lastMatchingFrame = childFrame;
}
childFrame = childFrame->GetNextSibling();
}
return lastMatchingFrame;
}
#ifdef DEBUG
void
nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame)
{
if (!aKidFrame)
return;
for (nsIFrame* cFrame : aKidFrame->PrincipalChildList()) {
nsTableRowFrame *rowFrame = do_QueryFrame(cFrame);
if (rowFrame) {
printf("row(%d)=%p ", rowFrame->GetRowIndex(),
static_cast<void*>(rowFrame));
for (nsIFrame* childFrame : cFrame->PrincipalChildList()) {
nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
if (cellFrame) {
uint32_t colIndex = cellFrame->ColIndex();
printf("cell(%u)=%p ", colIndex, static_cast<void*>(childFrame));
}
}
printf("\n");
}
else {
DumpRowGroup(rowFrame);
}
}
}
void
nsTableFrame::Dump(bool aDumpRows,
bool aDumpCols,
bool aDumpCellMap)
{
printf("***START TABLE DUMP*** \n");
// dump the columns widths array
printf("mColWidths=");
int32_t numCols = GetColCount();
int32_t colIdx;
nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
for (colIdx = 0; colIdx < numCols; colIdx++) {
printf("%d ", fif->GetColumnISizeFromFirstInFlow(colIdx));
}
printf("\n");
if (aDumpRows) {
nsIFrame* kidFrame = mFrames.FirstChild();
while (kidFrame) {
DumpRowGroup(kidFrame);
kidFrame = kidFrame->GetNextSibling();
}
}
if (aDumpCols) {
// output col frame cache
printf("\n col frame cache ->");
for (colIdx = 0; colIdx < numCols; colIdx++) {
nsTableColFrame* colFrame = mColFrames.ElementAt(colIdx);
if (0 == (colIdx % 8)) {
printf("\n");
}
printf ("%d=%p ", colIdx, static_cast<void*>(colFrame));
nsTableColType colType = colFrame->GetColType();
switch (colType) {
case eColContent:
printf(" content ");
break;
case eColAnonymousCol:
printf(" anonymous-column ");
break;
case eColAnonymousColGroup:
printf(" anonymous-colgroup ");
break;
case eColAnonymousCell:
printf(" anonymous-cell ");
break;
}
}
printf("\n colgroups->");
for (nsIFrame* childFrame : mColGroups) {
if (LayoutFrameType::TableColGroup == childFrame->Type()) {
nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame *)childFrame;
colGroupFrame->Dump(1);
}
}
for (colIdx = 0; colIdx < numCols; colIdx++) {
printf("\n");
nsTableColFrame* colFrame = GetColFrame(colIdx);
colFrame->Dump(1);
}
}
if (aDumpCellMap) {
nsTableCellMap* cellMap = GetCellMap();
cellMap->Dump();
}
printf(" ***END TABLE DUMP*** \n");
}
#endif
bool
nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const
{
// Since fixed-layout tables should not have their column sizes change
// as they load, we assume that all columns are significant.
if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed)
return true;
// the first column is always significant
if (aColIndex == 0)
return true;
nsTableCellMap* cellMap = GetCellMap();
if (!cellMap)
return false;
return cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0;
}
/********************************************************************************
* Collapsing Borders
*
* The CSS spec says to resolve border conflicts in this order:
* 1) any border with the style HIDDEN wins
* 2) the widest border with a style that is not NONE wins
* 3) the border styles are ranked in this order, highest to lowest precedence:
* double, solid, dashed, dotted, ridge, outset, groove, inset
* 4) borders that are of equal width and style (differ only in color) have this precedence:
* cell, row, rowgroup, col, colgroup, table
* 5) if all border styles are NONE, then that's the computed border style.
*******************************************************************************/
#ifdef DEBUG
#define VerifyNonNegativeDamageRect(r) \
NS_ASSERTION((r).StartCol() >= 0, "negative col index"); \
NS_ASSERTION((r).StartRow() >= 0, "negative row index"); \
NS_ASSERTION((r).ColCount() >= 0, "negative cols damage"); \
NS_ASSERTION((r).RowCount() >= 0, "negative rows damage");
#define VerifyDamageRect(r) \
VerifyNonNegativeDamageRect(r); \
NS_ASSERTION((r).EndCol() <= GetColCount(), \
"cols damage extends outside table"); \
NS_ASSERTION((r).EndRow() <= GetRowCount(), \
"rows damage extends outside table");
#endif
void
nsTableFrame::AddBCDamageArea(const TableArea& aValue)
{
NS_ASSERTION(IsBorderCollapse(), "invalid AddBCDamageArea call");
#ifdef DEBUG
VerifyDamageRect(aValue);
#endif
SetNeedToCalcBCBorders(true);
SetNeedToCalcHasBCBorders(true);
// Get the property
BCPropertyData* value = GetOrCreateBCProperty();
if (value) {
#ifdef DEBUG
VerifyNonNegativeDamageRect(value->mDamageArea);
#endif
// Clamp the old damage area to the current table area in case it shrunk.
int32_t cols = GetColCount();
if (value->mDamageArea.EndCol() > cols) {
if (value->mDamageArea.StartCol() > cols) {
value->mDamageArea.StartCol() = cols;
value->mDamageArea.ColCount() = 0;
}
else {
value->mDamageArea.ColCount() = cols - value->mDamageArea.StartCol();
}
}
int32_t rows = GetRowCount();
if (value->mDamageArea.EndRow() > rows) {
if (value->mDamageArea.StartRow() > rows) {
value->mDamageArea.StartRow() = rows;
value->mDamageArea.RowCount() = 0;
}
else {
value->mDamageArea.RowCount() = rows - value->mDamageArea.StartRow();
}
}
// Construct a union of the new and old damage areas.
value->mDamageArea.UnionArea(value->mDamageArea, aValue);
}
}
void
nsTableFrame::SetFullBCDamageArea()
{
NS_ASSERTION(IsBorderCollapse(), "invalid SetFullBCDamageArea call");
SetNeedToCalcBCBorders(true);
SetNeedToCalcHasBCBorders(true);
BCPropertyData* value = GetOrCreateBCProperty();
if (value) {
value->mDamageArea = TableArea(0, 0, GetColCount(), GetRowCount());
}
}
/* BCCellBorder represents a border segment which can be either an inline-dir
* or a block-dir segment. For each segment we need to know the color, width,
* style, who owns it and how long it is in cellmap coordinates.
* Ownership of these segments is important to calculate which corners should
* be bevelled. This structure has dual use, its used first to compute the
* dominant border for inline-dir and block-dir segments and to store the
* preliminary computed border results in the BCCellBorders structure.
* This temporary storage is not symmetric with respect to inline-dir and
* block-dir border segments, its always column oriented. For each column in
* the cellmap there is a temporary stored block-dir and inline-dir segment.
* XXX_Bernd this asymmetry is the root of those rowspan bc border errors
*/
struct BCCellBorder
{
BCCellBorder() { Reset(0, 1); }
void Reset(uint32_t aRowIndex, uint32_t aRowSpan);
nscolor color; // border segment color
BCPixelSize width; // border segment width in pixel coordinates !!
uint8_t style; // border segment style, possible values are defined
// in nsStyleConsts.h as NS_STYLE_BORDER_STYLE_*
BCBorderOwner owner; // border segment owner, possible values are defined
// in celldata.h. In the cellmap for each border
// segment we store the owner and later when
// painting we know the owner and can retrieve the
// style info from the corresponding frame
int32_t rowIndex; // rowIndex of temporary stored inline-dir border
// segments relative to the table
int32_t rowSpan; // row span of temporary stored inline-dir border
// segments
};
void
BCCellBorder::Reset(uint32_t aRowIndex,
uint32_t aRowSpan)
{
style = NS_STYLE_BORDER_STYLE_NONE;
color = 0;
width = 0;
owner = eTableOwner;
rowIndex = aRowIndex;
rowSpan = aRowSpan;
}
class BCMapCellIterator;
/*****************************************************************
* BCMapCellInfo
* This structure stores information about the cellmap and all involved
* table related frames that are used during the computation of winning borders
* in CalcBCBorders so that they do need to be looked up again and again when
* iterating over the cells.
****************************************************************/
struct BCMapCellInfo
{
explicit BCMapCellInfo(nsTableFrame* aTableFrame);
void ResetCellInfo();
void SetInfo(nsTableRowFrame* aNewRow,
int32_t aColIndex,
BCCellData* aCellData,
BCMapCellIterator* aIter,
nsCellMap* aCellMap = nullptr);
// The BCMapCellInfo has functions to set the continous
// border widths (see nsTablePainter.cpp for a description of the continous
// borders concept). The widths are computed inside these functions based on
// the current position inside the table and the cached frames that correspond
// to this position. The widths are stored in member variables of the internal
// table frames.
void SetTableBStartIStartContBCBorder();
void SetRowGroupIStartContBCBorder();
void SetRowGroupIEndContBCBorder();
void SetRowGroupBEndContBCBorder();
void SetRowIStartContBCBorder();
void SetRowIEndContBCBorder();
void SetColumnBStartIEndContBCBorder();
void SetColumnBEndContBCBorder();
void SetColGroupBEndContBCBorder();
void SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup,
nsTableRowFrame* aNextRow);
// functions to set the border widths on the table related frames, where the
// knowledge about the current position in the table is used.
void SetTableBStartBorderWidth(BCPixelSize aWidth);
void SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth);
void SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth);
void SetTableBEndBorderWidth(BCPixelSize aWidth);
void SetIStartBorderWidths(BCPixelSize aWidth);
void SetIEndBorderWidths(BCPixelSize aWidth);
void SetBStartBorderWidths(BCPixelSize aWidth);
void SetBEndBorderWidths(BCPixelSize aWidth);
// functions to compute the borders; they depend on the
// knowledge about the current position in the table. The edge functions
// should be called if a table edge is involved, otherwise the internal
// functions should be called.
BCCellBorder GetBStartEdgeBorder();
BCCellBorder GetBEndEdgeBorder();
BCCellBorder GetIStartEdgeBorder();
BCCellBorder GetIEndEdgeBorder();
BCCellBorder GetIEndInternalBorder();
BCCellBorder GetIStartInternalBorder();
BCCellBorder GetBStartInternalBorder();
BCCellBorder GetBEndInternalBorder();
// functions to set the internal position information
void SetColumn(int32_t aColX);
// Increment the row as we loop over the rows of a rowspan
void IncrementRow(bool aResetToBStartRowOfCell = false);
// Helper functions to get extent of the cell
int32_t GetCellEndRowIndex() const;
int32_t GetCellEndColIndex() const;
// storage of table information
nsTableFrame* mTableFrame;
int32_t mNumTableRows;
int32_t mNumTableCols;
BCPropertyData* mTableBCData;
WritingMode mTableWM;
// a cell can only belong to one rowgroup
nsTableRowGroupFrame* mRowGroup;
// a cell with a rowspan has a bstart and a bend row, and rows in between
nsTableRowFrame* mStartRow;
nsTableRowFrame* mEndRow;
nsTableRowFrame* mCurrentRowFrame;
// a cell with a colspan has an istart and iend column and columns in between
// they can belong to different colgroups
nsTableColGroupFrame* mColGroup;
nsTableColGroupFrame* mCurrentColGroupFrame;
nsTableColFrame* mStartCol;
nsTableColFrame* mEndCol;
nsTableColFrame* mCurrentColFrame;
// cell information
BCCellData* mCellData;
nsBCTableCellFrame* mCell;
int32_t mRowIndex;
int32_t mRowSpan;
int32_t mColIndex;
int32_t mColSpan;
// flags to describe the position of the cell with respect to the row- and
// colgroups, for instance mRgAtStart documents that the bStart cell border hits
// a rowgroup border
bool mRgAtStart;
bool mRgAtEnd;
bool mCgAtStart;
bool mCgAtEnd;
};
BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame)
: mTableFrame(aTableFrame)
, mNumTableRows(aTableFrame->GetRowCount())
, mNumTableCols(aTableFrame->GetColCount())
, mTableBCData(mTableFrame->GetProperty(TableBCProperty()))
, mTableWM(aTableFrame->StyleContext())
{
ResetCellInfo();
}
void
BCMapCellInfo::ResetCellInfo()
{
mCellData = nullptr;
mRowGroup = nullptr;
mStartRow = nullptr;
mEndRow = nullptr;
mColGroup = nullptr;
mStartCol = nullptr;
mEndCol = nullptr;
mCell = nullptr;
mRowIndex = mRowSpan = mColIndex = mColSpan = 0;
mRgAtStart = mRgAtEnd = mCgAtStart = mCgAtEnd = false;
}
inline int32_t
BCMapCellInfo::GetCellEndRowIndex() const
{
return mRowIndex + mRowSpan - 1;
}
inline int32_t
BCMapCellInfo::GetCellEndColIndex() const
{
return mColIndex + mColSpan - 1;
}
class BCMapCellIterator
{
public:
BCMapCellIterator(nsTableFrame* aTableFrame,
const TableArea& aDamageArea);
void First(BCMapCellInfo& aMapCellInfo);
void Next(BCMapCellInfo& aMapCellInfo);
void PeekIEnd(BCMapCellInfo& aRefInfo,
uint32_t aRowIndex,
BCMapCellInfo& aAjaInfo);
void PeekBEnd(BCMapCellInfo& aRefInfo,
uint32_t aColIndex,
BCMapCellInfo& aAjaInfo);
bool IsNewRow() { return mIsNewRow; }
nsTableRowFrame* GetPrevRow() const { return mPrevRow; }
nsTableRowFrame* GetCurrentRow() const { return mRow; }
nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup; }
int32_t mRowGroupStart;
int32_t mRowGroupEnd;
bool mAtEnd;
nsCellMap* mCellMap;
private:
bool SetNewRow(nsTableRowFrame* row = nullptr);
bool SetNewRowGroup(bool aFindFirstDamagedRow);
nsTableFrame* mTableFrame;
nsTableCellMap* mTableCellMap;
nsTableFrame::RowGroupArray mRowGroups;
nsTableRowGroupFrame* mRowGroup;
int32_t mRowGroupIndex;
uint32_t mNumTableRows;
nsTableRowFrame* mRow;
nsTableRowFrame* mPrevRow;
bool mIsNewRow;
int32_t mRowIndex;
uint32_t mNumTableCols;
int32_t mColIndex;
nsPoint mAreaStart; // These are not really points in the usual
nsPoint mAreaEnd; // sense; they're column/row coordinates
// in the cell map.
};
BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame,
const TableArea& aDamageArea)
: mTableFrame(aTableFrame)
{
mTableCellMap = aTableFrame->GetCellMap();
mAreaStart.x = aDamageArea.StartCol();
mAreaStart.y = aDamageArea.StartRow();
mAreaEnd.x = aDamageArea.EndCol() - 1;
mAreaEnd.y = aDamageArea.EndRow() - 1;
mNumTableRows = mTableFrame->GetRowCount();
mRow = nullptr;
mRowIndex = 0;
mNumTableCols = mTableFrame->GetColCount();
mColIndex = 0;
mRowGroupIndex = -1;
// Get the ordered row groups
aTableFrame->OrderRowGroups(mRowGroups);
mAtEnd = true; // gets reset when First() is called
}
// fill fields that we need for border collapse computation on a given cell
void
BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow,
int32_t aColIndex,
BCCellData* aCellData,
BCMapCellIterator* aIter,
nsCellMap* aCellMap)
{
// fill the cell information
mCellData = aCellData;
mColIndex = aColIndex;
// initialize the row information if it was not previously set for cells in
// this row
mRowIndex = 0;
if (aNewRow) {
mStartRow = aNewRow;
mRowIndex = aNewRow->GetRowIndex();
}
// fill cell frame info and row information
mCell = nullptr;
mRowSpan = 1;
mColSpan = 1;
if (aCellData) {
mCell = static_cast<nsBCTableCellFrame*>(aCellData->GetCellFrame());
if (mCell) {
if (!mStartRow) {
mStartRow = mCell->GetTableRowFrame();
if (!mStartRow) ABORT0();
mRowIndex = mStartRow->GetRowIndex();
}
mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap);
mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap);
}
}
if (!mStartRow) {
mStartRow = aIter->GetCurrentRow();
}
if (1 == mRowSpan) {
mEndRow = mStartRow;
}
else {
mEndRow = mStartRow->GetNextRow();
if (mEndRow) {
for (int32_t span = 2; mEndRow && span < mRowSpan; span++) {
mEndRow = mEndRow->GetNextRow();
}
NS_ASSERTION(mEndRow, "spanned row not found");
}
else {
NS_ERROR("error in cell map");
mRowSpan = 1;
mEndRow = mStartRow;
}
}
// row group frame info
// try to reuse the rgStart and rgEnd from the iterator as calls to
// GetRowCount() are computationally expensive and should be avoided if
// possible
uint32_t rgStart = aIter->mRowGroupStart;
uint32_t rgEnd = aIter->mRowGroupEnd;
mRowGroup = mStartRow->GetTableRowGroupFrame();
if (mRowGroup != aIter->GetCurrentRowGroup()) {
rgStart = mRowGroup->GetStartRowIndex();
rgEnd = rgStart + mRowGroup->GetRowCount() - 1;
}
uint32_t rowIndex = mStartRow->GetRowIndex();
mRgAtStart = rgStart == rowIndex;
mRgAtEnd = rgEnd == rowIndex + mRowSpan - 1;
// col frame info
mStartCol = mTableFrame->GetColFrame(aColIndex);
if (!mStartCol) ABORT0();
mEndCol = mStartCol;
if (mColSpan > 1) {
nsTableColFrame* colFrame = mTableFrame->GetColFrame(aColIndex +
mColSpan -1);
if (!colFrame) ABORT0();
mEndCol = colFrame;
}
// col group frame info
mColGroup = mStartCol->GetTableColGroupFrame();
int32_t cgStart = mColGroup->GetStartColumnIndex();
int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1);
mCgAtStart = cgStart == aColIndex;
mCgAtEnd = cgEnd == aColIndex + mColSpan - 1;
}
bool
BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow)
{
mAtEnd = true;
mPrevRow = mRow;
if (aRow) {
mRow = aRow;
}
else if (mRow) {
mRow = mRow->GetNextRow();
}
if (mRow) {
mRowIndex = mRow->GetRowIndex();
// get to the first entry with an originating cell
int32_t rgRowIndex = mRowIndex - mRowGroupStart;
if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length())
ABORT1(false);
const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex];
for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) {
CellData* cellData = row.SafeElementAt(mColIndex);
if (!cellData) { // add a dead cell data
TableArea damageArea;
cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex,
false, 0, damageArea);
if (!cellData) ABORT1(false);
}
if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
break;
}
}
mIsNewRow = true;
mAtEnd = false;
}
else ABORT1(false);
return !mAtEnd;
}
bool
BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow)
{
mAtEnd = true;
int32_t numRowGroups = mRowGroups.Length();
mCellMap = nullptr;
for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) {
mRowGroup = mRowGroups[mRowGroupIndex];
int32_t rowCount = mRowGroup->GetRowCount();
mRowGroupStart = mRowGroup->GetStartRowIndex();
mRowGroupEnd = mRowGroupStart + rowCount - 1;
if (rowCount > 0) {
mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap);
if (!mCellMap) ABORT1(false);
nsTableRowFrame* firstRow = mRowGroup->GetFirstRow();
if (aFindFirstDamagedRow) {
if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) {
// the damage area starts in the row group
if (aFindFirstDamagedRow) {
// find the correct first damaged row
int32_t numRows = mAreaStart.y - mRowGroupStart;
for (int32_t i = 0; i < numRows; i++) {
firstRow = firstRow->GetNextRow();
if (!firstRow) ABORT1(false);
}
}
}
else {
continue;
}
}
if (SetNewRow(firstRow)) { // sets mAtEnd
break;
}
}
}
return !mAtEnd;
}
void
BCMapCellIterator::First(BCMapCellInfo& aMapInfo)
{
aMapInfo.ResetCellInfo();
SetNewRowGroup(true); // sets mAtEnd
while (!mAtEnd) {
if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) {
BCCellData* cellData =
static_cast<BCCellData*>(mCellMap->GetDataAt(mAreaStart.y -
mRowGroupStart,
mAreaStart.x));
if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this);
return;
}
else {
NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)) ,
"damage area expanded incorrectly");
}
}
SetNewRowGroup(true); // sets mAtEnd
}
}
void
BCMapCellIterator::Next(BCMapCellInfo& aMapInfo)
{
if (mAtEnd) ABORT0();
aMapInfo.ResetCellInfo();
mIsNewRow = false;
mColIndex++;
while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) {
for (; mColIndex <= mAreaEnd.x; mColIndex++) {
int32_t rgRowIndex = mRowIndex - mRowGroupStart;
BCCellData* cellData =
static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, mColIndex));
if (!cellData) { // add a dead cell data
TableArea damageArea;
cellData =
static_cast<BCCellData*>(mCellMap->AppendCell(*mTableCellMap, nullptr,
rgRowIndex, false, 0,
damageArea));
if (!cellData) ABORT0();
}
if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
aMapInfo.SetInfo(mRow, mColIndex, cellData, this);
return;
}
}
if (mRowIndex >= mRowGroupEnd) {
SetNewRowGroup(false); // could set mAtEnd
}
else {
SetNewRow(); // could set mAtEnd
}
}
mAtEnd = true;
}
void
BCMapCellIterator::PeekIEnd(BCMapCellInfo& aRefInfo,
uint32_t aRowIndex,
BCMapCellInfo& aAjaInfo)
{
aAjaInfo.ResetCellInfo();
int32_t colIndex = aRefInfo.mColIndex + aRefInfo.mColSpan;
uint32_t rgRowIndex = aRowIndex - mRowGroupStart;
BCCellData* cellData =
static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, colIndex));
if (!cellData) { // add a dead cell data
NS_ASSERTION(colIndex < mTableCellMap->GetColCount(), "program error");
TableArea damageArea;
cellData =
static_cast<BCCellData*>(mCellMap->AppendCell(*mTableCellMap, nullptr,
rgRowIndex, false, 0,
damageArea));
if (!cellData) ABORT0();
}
nsTableRowFrame* row = nullptr;
if (cellData->IsRowSpan()) {
rgRowIndex -= cellData->GetRowSpanOffset();
cellData =
static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, colIndex));
if (!cellData)
ABORT0();
}
else {
row = mRow;
}
aAjaInfo.SetInfo(row, colIndex, cellData, this);
}
void
BCMapCellIterator::PeekBEnd(BCMapCellInfo& aRefInfo,
uint32_t aColIndex,
BCMapCellInfo& aAjaInfo)
{
aAjaInfo.ResetCellInfo();
int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan;
int32_t rgRowIndex = rowIndex - mRowGroupStart;
nsTableRowGroupFrame* rg = mRowGroup;
nsCellMap* cellMap = mCellMap;
nsTableRowFrame* nextRow = nullptr;
if (rowIndex > mRowGroupEnd) {
int32_t nextRgIndex = mRowGroupIndex;
do {
nextRgIndex++;
rg = mRowGroups.SafeElementAt(nextRgIndex);
if (rg) {
cellMap = mTableCellMap->GetMapFor(rg, cellMap); if (!cellMap) ABORT0();
rgRowIndex = 0;
nextRow = rg->GetFirstRow();
}
}
while (rg && !nextRow);
if(!rg) return;
}
else {
// get the row within the same row group
nextRow = mRow;
for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) {
nextRow = nextRow->GetNextRow(); if (!nextRow) ABORT0();
}
}
BCCellData* cellData =
static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
if (!cellData) { // add a dead cell data
NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error");
TableArea damageArea;
cellData =
static_cast<BCCellData*>(cellMap->AppendCell(*mTableCellMap, nullptr,
rgRowIndex, false, 0,
damageArea));
if (!cellData) ABORT0();
}
if (cellData->IsColSpan()) {
aColIndex -= cellData->GetColSpanOffset();
cellData =
static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
}
aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap);
}
// Assign priorities to border styles. For example, styleToPriority(NS_STYLE_BORDER_STYLE_SOLID)
// will return the priority of NS_STYLE_BORDER_STYLE_SOLID.
static uint8_t styleToPriority[13] = { 0, // NS_STYLE_BORDER_STYLE_NONE
2, // NS_STYLE_BORDER_STYLE_GROOVE
4, // NS_STYLE_BORDER_STYLE_RIDGE
5, // NS_STYLE_BORDER_STYLE_DOTTED
6, // NS_STYLE_BORDER_STYLE_DASHED
7, // NS_STYLE_BORDER_STYLE_SOLID
8, // NS_STYLE_BORDER_STYLE_DOUBLE
1, // NS_STYLE_BORDER_STYLE_INSET
3, // NS_STYLE_BORDER_STYLE_OUTSET
9 };// NS_STYLE_BORDER_STYLE_HIDDEN
// priority rules follow CSS 2.1 spec
// 'hidden', 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove',
// and the lowest: 'inset'. none is even weaker
#define CELL_CORNER true
/** return the border style, border color and optionally the width in
* pixel for a given frame and side
* @param aFrame - query the info for this frame
* @param aTableWM - the writing-mode of the frame
* @param aSide - the side of the frame
* @param aStyle - the border style
* @param aColor - the border color
* @param aWidth - the border width in px
*/
static void
GetColorAndStyle(const nsIFrame* aFrame,
WritingMode aTableWM,
LogicalSide aSide,
uint8_t* aStyle,
nscolor* aColor,
BCPixelSize* aWidth = nullptr)
{
NS_PRECONDITION(aFrame, "null frame");
NS_PRECONDITION(aStyle && aColor, "null argument");
// initialize out arg
*aColor = 0;
if (aWidth) {
*aWidth = 0;
}
const nsStyleBorder* styleData = aFrame->StyleBorder();
mozilla::Side physicalSide = aTableWM.PhysicalSide(aSide);
*aStyle = styleData->GetBorderStyle(physicalSide);
if ((NS_STYLE_BORDER_STYLE_NONE == *aStyle) ||
(NS_STYLE_BORDER_STYLE_HIDDEN == *aStyle)) {
return;
}
*aColor = aFrame->StyleContext()->
GetVisitedDependentColor(nsStyleBorder::BorderColorFieldFor(physicalSide));
if (aWidth) {
nscoord width = styleData->GetComputedBorderWidth(physicalSide);
*aWidth = aFrame->PresContext()->AppUnitsToDevPixels(width);
}
}
/** coerce the paint style as required by CSS2.1
* @param aFrame - query the info for this frame
* @param aTableWM - the writing mode of the frame
* @param aSide - the side of the frame
* @param aStyle - the border style
* @param aColor - the border color
*/
static void
GetPaintStyleInfo(const nsIFrame* aFrame,
WritingMode aTableWM,
LogicalSide aSide,
uint8_t* aStyle,
nscolor* aColor)
{
GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor);
if (NS_STYLE_BORDER_STYLE_INSET == *aStyle) {
*aStyle = NS_STYLE_BORDER_STYLE_RIDGE;
} else if (NS_STYLE_BORDER_STYLE_OUTSET == *aStyle) {
*aStyle = NS_STYLE_BORDER_STYLE_GROOVE;
}
}
class nsDelayedCalcBCBorders : public Runnable {
public:
explicit nsDelayedCalcBCBorders(nsIFrame* aFrame)
: mozilla::Runnable("nsDelayedCalcBCBorders")
, mFrame(aFrame)
{
}
NS_IMETHOD Run() override {
if (mFrame) {
nsTableFrame* tableFrame = static_cast <nsTableFrame*>(mFrame.GetFrame());
if (tableFrame->NeedToCalcBCBorders()) {
tableFrame->CalcBCBorders();
}
}
return NS_OK;
}
private:
WeakFrame mFrame;
};
bool
nsTableFrame::BCRecalcNeeded(nsStyleContext* aOldStyleContext,
nsStyleContext* aNewStyleContext)
{
// Attention: the old style context is the one we're forgetting,
// and hence possibly completely bogus for GetStyle* purposes.
// We use PeekStyleData instead.
const nsStyleBorder* oldStyleData = aOldStyleContext->PeekStyleBorder();
if (!oldStyleData)
return false;
const nsStyleBorder* newStyleData = aNewStyleContext->StyleBorder();
nsChangeHint change = newStyleData->CalcDifference(*oldStyleData);
if (!change)
return false;
if (change & nsChangeHint_NeedReflow)
return true; // the caller only needs to mark the bc damage area
if (change & nsChangeHint_RepaintFrame) {
// we need to recompute the borders and the caller needs to mark
// the bc damage area
// XXX In principle this should only be necessary for border style changes
// However the bc painting code tries to maximize the drawn border segments
// so it stores in the cellmap where a new border segment starts and this
// introduces a unwanted cellmap data dependence on color
nsCOMPtr<nsIRunnable> evt = new nsDelayedCalcBCBorders(this);
nsresult rv =
GetContent()->OwnerDoc()->Dispatch(TaskCategory::Other, evt.forget());
return NS_SUCCEEDED(rv);
}
return false;
}
// Compare two border segments, this comparison depends whether the two
// segments meet at a corner and whether the second segment is inline-dir.
// The return value is whichever of aBorder1 or aBorder2 dominates.
static const BCCellBorder&
CompareBorders(bool aIsCorner, // Pass true for corner calculations
const BCCellBorder& aBorder1,
const BCCellBorder& aBorder2,
bool aSecondIsInlineDir,
bool* aFirstDominates = nullptr)
{
bool firstDominates = true;
if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder1.style) {
firstDominates = (aIsCorner) ? false : true;
}
else if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder2.style) {
firstDominates = (aIsCorner) ? true : false;
}
else if (aBorder1.width < aBorder2.width) {
firstDominates = false;
}
else if (aBorder1.width == aBorder2.width) {
if (styleToPriority[aBorder1.style] < styleToPriority[aBorder2.style]) {
firstDominates = false;
}
else if (styleToPriority[aBorder1.style] == styleToPriority[aBorder2.style]) {
if (aBorder1.owner == aBorder2.owner) {
firstDominates = !aSecondIsInlineDir;
}
else if (aBorder1.owner < aBorder2.owner) {
firstDominates = false;
}
}
}
if (aFirstDominates)
*aFirstDominates = firstDominates;
if (firstDominates)
return aBorder1;
return aBorder2;
}
/** calc the dominant border by considering the table, row/col group, row/col,
* cell.
* Depending on whether the side is block-dir or inline-dir and whether
* adjacent frames are taken into account the ownership of a single border
* segment is defined. The return value is the dominating border
* The cellmap stores only bstart and istart borders for each cellmap position.
* If the cell border is owned by the cell that is istart-wards of the border
* it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other
* scenarios with a adjacent owner.
* @param xxxFrame - the frame for style information, might be zero if
* it should not be considered
* @param aTableWM - the writing mode of the frame
* @param aSide - side of the frames that should be considered
* @param aAja - the border comparison takes place from the point of
* a frame that is adjacent to the cellmap entry, for
* when a cell owns its lower border it will be the
* adjacent owner as in the cellmap only bstart and
* istart borders are stored.
*/
static BCCellBorder
CompareBorders(const nsIFrame* aTableFrame,
const nsIFrame* aColGroupFrame,
const nsIFrame* aColFrame,
const nsIFrame* aRowGroupFrame,
const nsIFrame* aRowFrame,
const nsIFrame* aCellFrame,
WritingMode aTableWM,
LogicalSide aSide,
bool aAja)
{
BCCellBorder border, tempBorder;
bool inlineAxis = IsBlock(aSide);
// start with the table as dominant if present
if (aTableFrame) {
GetColorAndStyle(aTableFrame, aTableWM, aSide,
&border.style, &border.color, &border.width);
border.owner = eTableOwner;
if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
return border;
}
}
// see if the colgroup is dominant
if (aColGroupFrame) {
GetColorAndStyle(aColGroupFrame, aTableWM, aSide,
&tempBorder.style, &tempBorder.color, &tempBorder.width);
tempBorder.owner = aAja && !inlineAxis ? eAjaColGroupOwner : eColGroupOwner;
// pass here and below false for aSecondIsInlineDir as it is only used for corner calculations.
border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
return border;
}
}
// see if the col is dominant
if (aColFrame) {
GetColorAndStyle(aColFrame, aTableWM, aSide,
&tempBorder.style, &tempBorder.color, &tempBorder.width);
tempBorder.owner = aAja && !inlineAxis ? eAjaColOwner : eColOwner;
border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
return border;
}
}
// see if the rowgroup is dominant
if (aRowGroupFrame) {
GetColorAndStyle(aRowGroupFrame, aTableWM, aSide,
&tempBorder.style, &tempBorder.color, &tempBorder.width);
tempBorder.owner = aAja && inlineAxis ? eAjaRowGroupOwner : eRowGroupOwner;
border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
return border;
}
}
// see if the row is dominant
if (aRowFrame) {
GetColorAndStyle(aRowFrame, aTableWM, aSide,
&tempBorder.style, &tempBorder.color, &tempBorder.width);
tempBorder.owner = aAja && inlineAxis ? eAjaRowOwner : eRowOwner;
border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
return border;
}
}
// see if the cell is dominant
if (aCellFrame) {
GetColorAndStyle(aCellFrame, aTableWM, aSide,
&tempBorder.style, &tempBorder.color, &tempBorder.width);
tempBorder.owner = aAja ? eAjaCellOwner : eCellOwner;
border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
}
return border;
}
static bool
Perpendicular(mozilla::LogicalSide aSide1,
mozilla::LogicalSide aSide2)
{
return IsInline(aSide1) != IsInline(aSide2);
}
// XXX allocate this as number-of-cols+1 instead of number-of-cols+1 * number-of-rows+1
struct BCCornerInfo
{
BCCornerInfo() { ownerColor = 0; ownerWidth = subWidth = ownerElem = subSide =
subElem = hasDashDot = numSegs = bevel = 0; ownerSide = eLogicalSideBStart;
ownerStyle = 0xFF; subStyle = NS_STYLE_BORDER_STYLE_SOLID; }
void Set(mozilla::LogicalSide aSide,
BCCellBorder border);
void Update(mozilla::LogicalSide aSide,
BCCellBorder border);
nscolor ownerColor; // color of borderOwner
uint16_t ownerWidth; // pixel width of borderOwner
uint16_t subWidth; // pixel width of the largest border intersecting the border perpendicular
// to ownerSide
uint32_t ownerSide:2; // LogicalSide (e.g eLogicalSideBStart, etc) of the border
// owning the corner relative to the corner
uint32_t ownerElem:3; // elem type (e.g. eTable, eGroup, etc) owning the corner
uint32_t ownerStyle:8; // border style of ownerElem
uint32_t subSide:2; // side of border with subWidth relative to the corner
uint32_t subElem:3; // elem type (e.g. eTable, eGroup, etc) of sub owner
uint32_t subStyle:8; // border style of subElem
uint32_t hasDashDot:1; // does a dashed, dotted segment enter the corner, they cannot be beveled
uint32_t numSegs:3; // number of segments entering corner
uint32_t bevel:1; // is the corner beveled (uses the above two fields together with subWidth)
uint32_t unused:1;
};
void
BCCornerInfo::Set(mozilla::LogicalSide aSide,
BCCellBorder aBorder)
{
ownerElem = aBorder.owner;
ownerStyle = aBorder.style;
ownerWidth = aBorder.width;
ownerColor = aBorder.color;
ownerSide = aSide;
hasDashDot = 0;
numSegs = 0;
if (aBorder.width > 0) {
numSegs++;
hasDashDot = (NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) ||
(NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style);
}
bevel = 0;
subWidth = 0;
// the following will get set later
subSide = IsInline(aSide) ? eLogicalSideBStart : eLogicalSideIStart;
subElem = eTableOwner;
subStyle = NS_STYLE_BORDER_STYLE_SOLID;
}
void
BCCornerInfo::Update(mozilla::LogicalSide aSide,
BCCellBorder aBorder)
{
bool existingWins = false;
if (0xFF == ownerStyle) { // initial value indiating that it hasn't been set yet
Set(aSide, aBorder);
}
else {
bool isInline = IsInline(aSide); // relative to the corner
BCCellBorder oldBorder, tempBorder;
oldBorder.owner = (BCBorderOwner) ownerElem;
oldBorder.style = ownerStyle;
oldBorder.width = ownerWidth;
oldBorder.color = ownerColor;
LogicalSide oldSide = LogicalSide(ownerSide);
tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline, &existingWins);
ownerElem = tempBorder.owner;
ownerStyle = tempBorder.style;
ownerWidth = tempBorder.width;
ownerColor = tempBorder.color;
if (existingWins) { // existing corner is dominant
if (::Perpendicular(LogicalSide(ownerSide), aSide)) {
// see if the new sub info replaces the old
BCCellBorder subBorder;
subBorder.owner = (BCBorderOwner) subElem;
subBorder.style = subStyle;
subBorder.width = subWidth;
subBorder.color = 0; // we are not interested in subBorder color
bool firstWins;
tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline, &firstWins);
subElem = tempBorder.owner;
subStyle = tempBorder.style;
subWidth = tempBorder.width;
if (!firstWins) {
subSide = aSide;
}
}
}
else { // input args are dominant
ownerSide = aSide;
if (::Perpendicular(oldSide, LogicalSide(ownerSide))) {
subElem = oldBorder.owner;
subStyle = oldBorder.style;
subWidth = oldBorder.width;
subSide = oldSide;
}
}
if (aBorder.width > 0) {
numSegs++;
if (!hasDashDot && ((NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) ||
(NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style))) {
hasDashDot = 1;
}
}
// bevel the corner if only two perpendicular non dashed/dotted segments enter the corner
bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot);
}
}
struct BCCorners
{
BCCorners(int32_t aNumCorners,
int32_t aStartIndex);
~BCCorners() { delete [] corners; }
BCCornerInfo& operator [](int32_t i) const
{ NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
return corners[clamped(i, startIndex, endIndex) - startIndex]; }
int32_t startIndex;
int32_t endIndex;
BCCornerInfo* corners;
};
BCCorners::BCCorners(int32_t aNumCorners,
int32_t aStartIndex)
{
NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error");
startIndex = aStartIndex;
endIndex = aStartIndex + aNumCorners - 1;
corners = new BCCornerInfo[aNumCorners];
}
struct BCCellBorders
{
BCCellBorders(int32_t aNumBorders,
int32_t aStartIndex);
~BCCellBorders() { delete [] borders; }
BCCellBorder& operator [](int32_t i) const
{ NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
return borders[clamped(i, startIndex, endIndex) - startIndex]; }
int32_t startIndex;
int32_t endIndex;
BCCellBorder* borders;
};
BCCellBorders::BCCellBorders(int32_t aNumBorders,
int32_t aStartIndex)
{
NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error");
startIndex = aStartIndex;
endIndex = aStartIndex + aNumBorders - 1;
borders = new BCCellBorder[aNumBorders];
}
// this function sets the new border properties and returns true if the border
// segment will start a new segment and not be accumulated into the previous
// segment.
static bool
SetBorder(const BCCellBorder& aNewBorder,
BCCellBorder& aBorder)
{
bool changed = (aNewBorder.style != aBorder.style) ||
(aNewBorder.width != aBorder.width) ||
(aNewBorder.color != aBorder.color);
aBorder.color = aNewBorder.color;
aBorder.width = aNewBorder.width;
aBorder.style = aNewBorder.style;
aBorder.owner = aNewBorder.owner;
return changed;
}
// this function will set the inline-dir border. It will return true if the
// existing segment will not be continued. Having a block-dir owner of a corner
// should also start a new segment.
static bool
SetInlineDirBorder(const BCCellBorder& aNewBorder,
const BCCornerInfo& aCorner,
BCCellBorder& aBorder)
{
bool startSeg = ::SetBorder(aNewBorder, aBorder);
if (!startSeg) {
startSeg = !IsInline(LogicalSide(aCorner.ownerSide));
}
return startSeg;
}
// Make the damage area larger on the top and bottom by at least one row and on the left and right
// at least one column. This is done so that adjacent elements are part of the border calculations.
// The extra segments and borders outside the actual damage area will not be updated in the cell map,
// because they in turn would need info from adjacent segments outside the damage area to be accurate.
void
nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const
{
int32_t numRows = GetRowCount();
int32_t numCols = GetColCount();
int32_t dStartX = aArea.StartCol();
int32_t dEndX = aArea.EndCol() - 1;
int32_t dStartY = aArea.StartRow();
int32_t dEndY = aArea.EndRow() - 1;
// expand the damage area in each direction
if (dStartX > 0) {
dStartX--;
}
if (dEndX < (numCols - 1)) {
dEndX++;
}
if (dStartY > 0) {
dStartY--;
}
if (dEndY < (numRows - 1)) {
dEndY++;
}
// Check the damage area so that there are no cells spanning in or out. If there are any then
// make the damage area as big as the table, similarly to the way the cell map decides whether
// to rebuild versus expand. This could be optimized to expand to the smallest area that contains
// no spanners, but it may not be worth the effort in general, and it would need to be done in the
// cell map as well.
bool haveSpanner = false;
if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) || (dEndY < (numRows - 1))) {
nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0();
// Get the ordered row groups
RowGroupArray rowGroups;
OrderRowGroups(rowGroups);
// Scope outside loop to be used as hint.
nsCellMap* cellMap = nullptr;
for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
int32_t rgStartY = rgFrame->GetStartRowIndex();
int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1;
if (dEndY < rgStartY)
break;
cellMap = tableCellMap->GetMapFor(rgFrame, cellMap);
if (!cellMap) ABORT0();
// check for spanners from above and below
if ((dStartY > 0) && (dStartY >= rgStartY) && (dStartY <= rgEndY)) {
if (uint32_t(dStartY - rgStartY) >= cellMap->mRows.Length())
ABORT0();
const nsCellMap::CellDataArray& row =
cellMap->mRows[dStartY - rgStartY];
for (int32_t x = dStartX; x <= dEndX; x++) {
CellData* cellData = row.SafeElementAt(x);
if (cellData && (cellData->IsRowSpan())) {
haveSpanner = true;
break;
}
}
if (dEndY < rgEndY) {
if (uint32_t(dEndY + 1 - rgStartY) >= cellMap->mRows.Length())
ABORT0();
const nsCellMap::CellDataArray& row2 =
cellMap->mRows[dEndY + 1 - rgStartY];
for (int32_t x = dStartX; x <= dEndX; x++) {
CellData* cellData = row2.SafeElementAt(x);
if (cellData && (cellData->IsRowSpan())) {
haveSpanner = true;
break;
}
}
}
}
// check for spanners on the left and right
int32_t iterStartY = -1;
int32_t iterEndY = -1;
if ((dStartY >= rgStartY) && (dStartY <= rgEndY)) {
// the damage area starts in the row group
iterStartY = dStartY;
iterEndY = std::min(dEndY, rgEndY);
}
else if ((dEndY >= rgStartY) && (dEndY <= rgEndY)) {
// the damage area ends in the row group
iterStartY = rgStartY;
iterEndY = dEndY;
}
else if ((rgStartY >= dStartY) && (rgEndY <= dEndY)) {
// the damage area contains the row group
iterStartY = rgStartY;
iterEndY = rgEndY;
}
if ((iterStartY >= 0) && (iterEndY >= 0)) {
for (int32_t y = iterStartY; y <= iterEndY; y++) {
if (uint32_t(y - rgStartY) >= cellMap->mRows.Length())
ABORT0();
const nsCellMap::CellDataArray& row =
cellMap->mRows[y - rgStartY];
CellData* cellData = row.SafeElementAt(dStartX);
if (cellData && (cellData->IsColSpan())) {
haveSpanner = true;
break;
}
if (dEndX < (numCols - 1)) {
cellData = row.SafeElementAt(dEndX + 1);
if (cellData && (cellData->IsColSpan())) {
haveSpanner = true;
break;
}
}
}
}
}
}
if (haveSpanner) {
// make the damage area the whole table
aArea.StartCol() = 0;
aArea.StartRow() = 0;
aArea.ColCount() = numCols;
aArea.RowCount() = numRows;
}
else {
aArea.StartCol() = dStartX;
aArea.StartRow() = dStartY;
aArea.ColCount() = 1 + dEndX - dStartX;
aArea.RowCount() = 1 + dEndY - dStartY;
}
}
#define ADJACENT true
#define INLINE_DIR true
void
BCMapCellInfo::SetTableBStartIStartContBCBorder()
{
BCCellBorder currentBorder;
//calculate continuous top first row & rowgroup border: special case
//because it must include the table in the collapse
if (mStartRow) {
currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup,
mStartRow, nullptr, mTableWM,
eLogicalSideBStart, !ADJACENT);
mStartRow->SetContinuousBCBorderWidth(eLogicalSideBStart,
currentBorder.width);
}
if (mCgAtEnd && mColGroup) {
//calculate continuous top colgroup border once per colgroup
currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup,
mStartRow, nullptr, mTableWM,
eLogicalSideBStart, !ADJACENT);
mColGroup->SetContinuousBCBorderWidth(eLogicalSideBStart,
currentBorder.width);
}
if (0 == mColIndex) {
currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol, nullptr,
nullptr, nullptr, mTableWM,
eLogicalSideIStart, !ADJACENT);
mTableFrame->SetContinuousIStartBCBorderWidth(currentBorder.width);
}
}
void
BCMapCellInfo::SetRowGroupIStartContBCBorder()
{
BCCellBorder currentBorder;
//get row group continuous borders
if (mRgAtEnd && mRowGroup) { //once per row group, so check for bottom
currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol,
mRowGroup, nullptr, nullptr, mTableWM,
eLogicalSideIStart, !ADJACENT);
mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIStart,
currentBorder.width);
}
}
void
BCMapCellInfo::SetRowGroupIEndContBCBorder()
{
BCCellBorder currentBorder;
//get row group continuous borders
if (mRgAtEnd && mRowGroup) { //once per mRowGroup, so check for bottom
currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
nullptr, nullptr, mTableWM, eLogicalSideIEnd,
ADJACENT);
mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIEnd,
currentBorder.width);
}
}
void
BCMapCellInfo::SetColumnBStartIEndContBCBorder()
{
BCCellBorder currentBorder;
//calculate column continuous borders
//we only need to do this once, so we'll do it only on the first row
currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
mCurrentColFrame, mRowGroup, mStartRow,
nullptr, mTableWM, eLogicalSideBStart,
!ADJACENT);
mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBStart,
currentBorder.width);
if (mNumTableCols == GetCellEndColIndex() + 1) {
currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
mCurrentColFrame, nullptr, nullptr, nullptr,
mTableWM, eLogicalSideIEnd, !ADJACENT);
}
else {
currentBorder = CompareBorders(nullptr, mCurrentColGroupFrame,
mCurrentColFrame, nullptr,nullptr, nullptr,
mTableWM, eLogicalSideIEnd, !ADJACENT);
}
mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd,
currentBorder.width);
}
void
BCMapCellInfo::SetColumnBEndContBCBorder()
{
BCCellBorder currentBorder;
//get col continuous border
currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
mCurrentColFrame, mRowGroup, mEndRow,
nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBEnd,
currentBorder.width);
}
void
BCMapCellInfo::SetColGroupBEndContBCBorder()
{
BCCellBorder currentBorder;
if (mColGroup) {
currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup,
mEndRow, nullptr, mTableWM,
eLogicalSideBEnd, ADJACENT);
mColGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width);
}
}
void
BCMapCellInfo::SetRowGroupBEndContBCBorder()
{
BCCellBorder currentBorder;
if (mRowGroup) {
currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup,
mEndRow, nullptr, mTableWM,
eLogicalSideBEnd, ADJACENT);
mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width);
}
}
void
BCMapCellInfo::SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup,
nsTableRowFrame* aNextRow)
{
BCCellBorder currentBorder, adjacentBorder;
const nsIFrame* rowgroup = mRgAtEnd ? mRowGroup : nullptr;
currentBorder = CompareBorders(nullptr, nullptr, nullptr, rowgroup, mEndRow,
nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
adjacentBorder = CompareBorders(nullptr, nullptr, nullptr, aNextRowGroup,
aNextRow, nullptr, mTableWM, eLogicalSideBStart,
!ADJACENT);
currentBorder = CompareBorders(false, currentBorder, adjacentBorder,
INLINE_DIR);
if (aNextRow) {
aNextRow->SetContinuousBCBorderWidth(eLogicalSideBStart,
currentBorder.width);
}
if (mRgAtEnd && mRowGroup) {
mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width);
}
}
void
BCMapCellInfo::SetRowIStartContBCBorder()
{
//get row continuous borders
if (mCurrentRowFrame) {
BCCellBorder currentBorder;
currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol,
mRowGroup, mCurrentRowFrame, nullptr,
mTableWM, eLogicalSideIStart, !ADJACENT);
mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIStart,
currentBorder.width);
}
}
void
BCMapCellInfo::SetRowIEndContBCBorder()
{
if (mCurrentRowFrame) {
BCCellBorder currentBorder;
currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
mCurrentRowFrame, nullptr, mTableWM,
eLogicalSideIEnd, ADJACENT);
mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd,
currentBorder.width);
}
}
void
BCMapCellInfo::SetTableBStartBorderWidth(BCPixelSize aWidth)
{
mTableBCData->mBStartBorderWidth = std::max(mTableBCData->mBStartBorderWidth,
aWidth);
}
void
BCMapCellInfo::SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth)
{
// update the iStart first cell border
if (aRowB == 0) {
mTableBCData->