diff options
Diffstat (limited to '')
-rw-r--r-- | layout/tables/nsTableRowGroupFrame.cpp | 1864 |
1 files changed, 1864 insertions, 0 deletions
diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp new file mode 100644 index 0000000000..f8dac61386 --- /dev/null +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -0,0 +1,1864 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsTableRowGroupFrame.h" + +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" + +#include "nsCOMPtr.h" +#include "nsTableRowFrame.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsPresContext.h" +#include "nsStyleConsts.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsGkAtoms.h" +#include "nsCSSRendering.h" +#include "nsHTMLParts.h" +#include "nsCSSFrameConstructor.h" +#include "nsDisplayList.h" + +#include "nsCellMap.h" //table cell navigation +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::layout; + +namespace mozilla { + +struct TableRowGroupReflowInput final { + // Our reflow input + const ReflowInput& mReflowInput; + + // The available size (computed from the parent) + LogicalSize mAvailSize; + + // Running block-offset + nscoord mBCoord = 0; + + explicit TableRowGroupReflowInput(const ReflowInput& aReflowInput) + : mReflowInput(aReflowInput), mAvailSize(aReflowInput.AvailableSize()) {} + + ~TableRowGroupReflowInput() = default; +}; + +} // namespace mozilla + +nsTableRowGroupFrame::nsTableRowGroupFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsContainerFrame(aStyle, aPresContext, kClassID) { + SetRepeatable(false); +} + +nsTableRowGroupFrame::~nsTableRowGroupFrame() = default; + +void nsTableRowGroupFrame::Destroy(DestroyContext& aContext) { + nsTableFrame::MaybeUnregisterPositionedTablePart(this); + nsContainerFrame::Destroy(aContext); +} + +NS_QUERYFRAME_HEAD(nsTableRowGroupFrame) + NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +int32_t nsTableRowGroupFrame::GetRowCount() const { +#ifdef DEBUG + for (nsIFrame* f : mFrames) { + NS_ASSERTION(f->StyleDisplay()->mDisplay == mozilla::StyleDisplay::TableRow, + "Unexpected display"); + NS_ASSERTION(f->IsTableRowFrame(), "Unexpected frame type"); + } +#endif + + return mFrames.GetLength(); +} + +int32_t nsTableRowGroupFrame::GetStartRowIndex() const { + int32_t result = -1; + if (mFrames.NotEmpty()) { + NS_ASSERTION(mFrames.FirstChild()->IsTableRowFrame(), + "Unexpected frame type"); + result = static_cast<nsTableRowFrame*>(mFrames.FirstChild())->GetRowIndex(); + } + // if the row group doesn't have any children, get it the hard way + if (-1 == result) { + return GetTableFrame()->GetStartRowIndex(this); + } + + return result; +} + +void nsTableRowGroupFrame::AdjustRowIndices(int32_t aRowIndex, + int32_t anAdjustment) { + for (nsIFrame* rowFrame : mFrames) { + if (mozilla::StyleDisplay::TableRow == rowFrame->StyleDisplay()->mDisplay) { + int32_t index = ((nsTableRowFrame*)rowFrame)->GetRowIndex(); + if (index >= aRowIndex) + ((nsTableRowFrame*)rowFrame)->SetRowIndex(index + anAdjustment); + } + } +} + +int32_t nsTableRowGroupFrame::GetAdjustmentForStoredIndex( + int32_t aStoredIndex) { + nsTableFrame* tableFrame = GetTableFrame(); + return tableFrame->GetAdjustmentForStoredIndex(aStoredIndex); +} + +void nsTableRowGroupFrame::MarkRowsAsDeleted(nsTableRowFrame& aStartRowFrame, + int32_t aNumRowsToDelete) { + nsTableRowFrame* currentRowFrame = &aStartRowFrame; + for (;;) { + // XXXneerja - Instead of calling AddDeletedRowIndex() per row frame + // it is possible to change AddDeleteRowIndex to instead take + // <start row index> and <num of rows to mark for deletion> as arguments. + // The problem that emerges here is mDeletedRowIndexRanges only stores + // disjoint index ranges and since AddDeletedRowIndex() must operate on + // the "stored" index, in some cases it is possible that the range + // of indices to delete becomes overlapping EG: Deleting rows 9 - 11 and + // then from the remaining rows deleting the *new* rows 7 to 20. + // Handling these overlapping ranges is much more complicated to + // implement and so I opted to add the deleted row index of one row at a + // time and maintain the invariant that the range of deleted row indices + // is always disjoint. + currentRowFrame->AddDeletedRowIndex(); + if (--aNumRowsToDelete == 0) { + break; + } + currentRowFrame = do_QueryFrame(currentRowFrame->GetNextSibling()); + if (!currentRowFrame) { + MOZ_ASSERT_UNREACHABLE("expected another row frame"); + break; + } + } +} + +void nsTableRowGroupFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) { + nsTableFrame* tableFrame = GetTableFrame(); + return tableFrame->AddDeletedRowIndex(aDeletedRowStoredIndex); +} + +void nsTableRowGroupFrame::InitRepeatedFrame( + nsTableRowGroupFrame* aHeaderFooterFrame) { + nsTableRowFrame* copyRowFrame = GetFirstRow(); + nsTableRowFrame* originalRowFrame = aHeaderFooterFrame->GetFirstRow(); + AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP); + while (copyRowFrame && originalRowFrame) { + copyRowFrame->AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP); + int rowIndex = originalRowFrame->GetRowIndex(); + copyRowFrame->SetRowIndex(rowIndex); + + // For each table cell frame set its column index + nsTableCellFrame* originalCellFrame = originalRowFrame->GetFirstCell(); + nsTableCellFrame* copyCellFrame = copyRowFrame->GetFirstCell(); + while (copyCellFrame && originalCellFrame) { + NS_ASSERTION( + originalCellFrame->GetContent() == copyCellFrame->GetContent(), + "cell frames have different content"); + uint32_t colIndex = originalCellFrame->ColIndex(); + copyCellFrame->SetColIndex(colIndex); + + // Move to the next cell frame + copyCellFrame = copyCellFrame->GetNextCell(); + originalCellFrame = originalCellFrame->GetNextCell(); + } + + // Move to the next row frame + originalRowFrame = originalRowFrame->GetNextRow(); + copyRowFrame = copyRowFrame->GetNextRow(); + } +} + +// Handle the child-traversal part of DisplayGenericTablePart +static void DisplayRows(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aLists) { + nscoord overflowAbove; + nsTableRowGroupFrame* f = static_cast<nsTableRowGroupFrame*>(aFrame); + // Don't try to use the row cursor if we have to descend into placeholders; + // we might have rows containing placeholders, where the row's overflow + // area doesn't intersect the dirty rect but we need to descend into the row + // to see out of flows. + // Note that we really want to check ShouldDescendIntoFrame for all + // the rows in |f|, but that's exactly what we're trying to avoid, so we + // approximate it by checking it for |f|: if it's true for any row + // in |f| then it's true for |f| itself. + nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(f, true) + ? nullptr + : f->GetFirstRowContaining(aBuilder->GetVisibleRect().y, + &overflowAbove); + + if (kid) { + // have a cursor, use it + while (kid) { + if (kid->GetRect().y - overflowAbove >= + aBuilder->GetVisibleRect().YMost()) { + break; + } + f->BuildDisplayListForChild(aBuilder, kid, aLists); + kid = kid->GetNextSibling(); + } + return; + } + + // No cursor. Traverse children the hard way and build a cursor while we're at + // it + nsTableRowGroupFrame::FrameCursorData* cursor = f->SetupRowCursor(); + kid = f->PrincipalChildList().FirstChild(); + while (kid) { + f->BuildDisplayListForChild(aBuilder, kid, aLists); + + if (cursor) { + if (!cursor->AppendFrame(kid)) { + f->ClearRowCursor(); + return; + } + } + + kid = kid->GetNextSibling(); + } + if (cursor) { + cursor->FinishBuildingCursor(); + } +} + +void nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground()); + + for (nsTableRowFrame* row = GetFirstRow(); row; row = row->GetNextRow()) { + if (!aBuilder->GetDirtyRect().Intersects(row->InkOverflowRect() + + row->GetNormalPosition())) { + continue; + } + row->PaintCellBackgroundsForFrame(this, aBuilder, aLists, + row->GetNormalPosition()); + } + + DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground()); + + DisplayOutline(aBuilder, aLists); + + DisplayRows(aBuilder, this, aLists); +} + +LogicalSides nsTableRowGroupFrame::GetLogicalSkipSides() const { + LogicalSides skip(mWritingMode); + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return skip; + } + + if (GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +// Position and size aKidFrame and update our reflow input. +void nsTableRowGroupFrame::PlaceChild( + nsPresContext* aPresContext, TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame, const ReflowInput& aKidReflowInput, WritingMode aWM, + const LogicalPoint& aKidPosition, const nsSize& aContainerSize, + ReflowOutput& aDesiredSize, const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidInkOverflow) { + bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + // Place and size the child + FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, &aKidReflowInput, + aWM, aKidPosition, aContainerSize, + ReflowChildFlags::ApplyRelativePositioning); + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse()) { + nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, + aOriginalKidInkOverflow, isFirstReflow); + } + + // Adjust the running block-offset + aReflowInput.mBCoord += aDesiredSize.BSize(aWM); + + // If our block-size is constrained then update the available bsize + if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(aWM)) { + aReflowInput.mAvailSize.BSize(aWM) -= aDesiredSize.BSize(aWM); + } +} + +void nsTableRowGroupFrame::InitChildReflowInput(nsPresContext* aPresContext, + bool aBorderCollapse, + ReflowInput& aReflowInput) { + const auto childWM = aReflowInput.GetWritingMode(); + LogicalMargin border(childWM); + if (aBorderCollapse) { + auto* rowFrame = static_cast<nsTableRowFrame*>(aReflowInput.mFrame); + border = rowFrame->GetBCBorderWidth(childWM); + } + const LogicalMargin zeroPadding(childWM); + aReflowInput.Init(aPresContext, Nothing(), Some(border), Some(zeroPadding)); +} + +static void CacheRowBSizesForPrinting(nsTableRowFrame* aFirstRow, + WritingMode aWM) { + for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) { + if (!row->GetPrevInFlow()) { + row->SetUnpaginatedBSize(row->BSize(aWM)); + } + } +} + +void nsTableRowGroupFrame::ReflowChildren( + nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + TableRowGroupReflowInput& aReflowInput, nsReflowStatus& aStatus, + bool* aPageBreakBeforeEnd) { + if (aPageBreakBeforeEnd) { + *aPageBreakBeforeEnd = false; + } + + WritingMode wm = aReflowInput.mReflowInput.GetWritingMode(); + nsTableFrame* tableFrame = GetTableFrame(); + const bool borderCollapse = tableFrame->IsBorderCollapse(); + + // XXXldb Should we really be checking IsPaginated(), + // or should we *only* check available block-size? + // (Think about multi-column layout!) + bool isPaginated = aPresContext->IsPaginated() && + NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm); + + bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() || + tableFrame->IsGeometryDirty() || + tableFrame->NeedToCollapse(); + + // in vertical-rl mode, we always need the row bsizes in order to + // get the necessary containerSize for placing our kids + bool needToCalcRowBSizes = reflowAllKids || wm.IsVerticalRL(); + + nsSize containerSize = + aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); + + nsIFrame* prevKidFrame = nullptr; + for (nsTableRowFrame* kidFrame = GetFirstRow(); kidFrame; + prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextRow()) { + const nscoord rowSpacing = + tableFrame->GetRowSpacing(kidFrame->GetRowIndex()); + + // Reflow the row frame + if (reflowAllKids || kidFrame->IsSubtreeDirty() || + (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow && + (isPaginated || + kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { + LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize); + nsRect oldKidInkOverflow = kidFrame->InkOverflowRect(); + + ReflowOutput kidDesiredSize(aReflowInput.mReflowInput); + + // Reflow the child into the available space, giving it as much bsize as + // it wants. We'll deal with splitting later after we've computed the row + // bsizes, taking into account cells with row spans... + LogicalSize kidAvailSize = aReflowInput.mAvailSize; + kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput kidReflowInput(aPresContext, aReflowInput.mReflowInput, + kidFrame, kidAvailSize, Nothing(), + ReflowInput::InitFlag::CallerWillInit); + InitChildReflowInput(aPresContext, borderCollapse, kidReflowInput); + + // This can indicate that columns were resized. + if (aReflowInput.mReflowInput.IsIResize()) { + kidReflowInput.SetIResize(true); + } + + NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame, + "If we're not on the first frame, we should have a " + "previous sibling..."); + // If prev row has nonzero YMost, then we can't be at the top of the page + if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) { + kidReflowInput.mFlags.mIsTopOfPage = false; + } + + LogicalPoint kidPosition(wm, 0, aReflowInput.mBCoord); + ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, wm, + kidPosition, containerSize, ReflowChildFlags::Default, + aStatus); + + // Place the child + PlaceChild(aPresContext, aReflowInput, kidFrame, kidReflowInput, wm, + kidPosition, containerSize, kidDesiredSize, + oldKidRect.GetPhysicalRect(wm, containerSize), + oldKidInkOverflow); + aReflowInput.mBCoord += rowSpacing; + + if (!reflowAllKids) { + if (IsSimpleRowFrame(tableFrame, kidFrame)) { + // Inform the row of its new bsize. + kidFrame->DidResize(); + // the overflow area may have changed inflate the overflow area + const nsStylePosition* stylePos = StylePosition(); + if (tableFrame->IsAutoBSize(wm) && + !stylePos->BSize(wm).ConvertsToLength()) { + // Because other cells in the row may need to be aligned + // differently, repaint the entire row + InvalidateFrame(); + } else if (oldKidRect.BSize(wm) != kidDesiredSize.BSize(wm)) { + needToCalcRowBSizes = true; + } + } else { + needToCalcRowBSizes = true; + } + } + + if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) { + nsTableRowFrame* nextRow = kidFrame->GetNextRow(); + if (nextRow) { + *aPageBreakBeforeEnd = + nsTableFrame::PageBreakAfter(kidFrame, nextRow); + } + } + } else { + // Move a child that was skipped during a reflow. + const LogicalPoint oldPosition = + kidFrame->GetLogicalNormalPosition(wm, containerSize); + if (oldPosition.B(wm) != aReflowInput.mBCoord) { + kidFrame->InvalidateFrameSubtree(); + const LogicalPoint offset(wm, 0, + aReflowInput.mBCoord - oldPosition.B(wm)); + kidFrame->MovePositionBy(wm, offset); + nsTableFrame::RePositionViews(kidFrame); + kidFrame->InvalidateFrameSubtree(); + } + + // Adjust the running b-offset so we know where the next row should be + // placed + nscoord bSize = kidFrame->BSize(wm) + rowSpacing; + aReflowInput.mBCoord += bSize; + + if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm)) { + aReflowInput.mAvailSize.BSize(wm) -= bSize; + } + } + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + } + + if (GetFirstRow()) { + aReflowInput.mBCoord -= + tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount()); + } + + // Return our desired rect + aDesiredSize.ISize(wm) = aReflowInput.mReflowInput.AvailableISize(); + aDesiredSize.BSize(wm) = aReflowInput.mBCoord; + + if (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow) { + DidResizeRows(aDesiredSize); + if (isPaginated) { + CacheRowBSizesForPrinting(GetFirstRow(), wm); + } + } else if (needToCalcRowBSizes) { + CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.mReflowInput); + if (!reflowAllKids) { + InvalidateFrame(); + } + } +} + +nsTableRowFrame* nsTableRowGroupFrame::GetFirstRow() const { + nsIFrame* firstChild = mFrames.FirstChild(); + MOZ_ASSERT( + !firstChild || static_cast<nsTableRowFrame*>(do_QueryFrame(firstChild)), + "How do we have a non-row child?"); + return static_cast<nsTableRowFrame*>(firstChild); +} + +nsTableRowFrame* nsTableRowGroupFrame::GetLastRow() const { + nsIFrame* lastChild = mFrames.LastChild(); + MOZ_ASSERT( + !lastChild || static_cast<nsTableRowFrame*>(do_QueryFrame(lastChild)), + "How do we have a non-row child?"); + return static_cast<nsTableRowFrame*>(lastChild); +} + +struct RowInfo { + RowInfo() { bSize = pctBSize = hasStyleBSize = hasPctBSize = isSpecial = 0; } + unsigned bSize; // content bsize or fixed bsize, excluding pct bsize + unsigned pctBSize : 29; // pct bsize + unsigned hasStyleBSize : 1; + unsigned hasPctBSize : 1; + unsigned isSpecial : 1; // there is no cell originating in the row with + // rowspan=1 and there are at least 2 cells spanning + // the row and there is no style bsize on the row +}; + +static void UpdateBSizes(RowInfo& aRowInfo, nscoord aAdditionalBSize, + nscoord& aTotal, nscoord& aUnconstrainedTotal) { + aRowInfo.bSize += aAdditionalBSize; + aTotal += aAdditionalBSize; + if (!aRowInfo.hasStyleBSize) { + aUnconstrainedTotal += aAdditionalBSize; + } +} + +void nsTableRowGroupFrame::DidResizeRows(ReflowOutput& aDesiredSize) { + // Update the cells spanning rows with their new bsizes. + // This is the place where all of the cells in the row get set to the bsize + // of the row. + // Reset the overflow area. + aDesiredSize.mOverflowAreas.Clear(); + for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame; + rowFrame = rowFrame->GetNextRow()) { + rowFrame->DidResize(); + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame); + } +} + +// This calculates the bsize of all the rows and takes into account +// style bsize on the row group, style bsizes on rows and cells, style bsizes on +// rowspans. Actual row bsizes will be adjusted later if the table has a style +// bsize. Even if rows don't change bsize, this method must be called to set the +// bsizes of each cell in the row to the bsize of its row. +void nsTableRowGroupFrame::CalculateRowBSizes(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput) { + nsTableFrame* tableFrame = GetTableFrame(); + const bool isPaginated = aPresContext->IsPaginated(); + + int32_t numEffCols = tableFrame->GetEffectiveColCount(); + + int32_t startRowIndex = GetStartRowIndex(); + // find the row corresponding to the row index we just found + nsTableRowFrame* startRowFrame = GetFirstRow(); + + if (!startRowFrame) { + return; + } + + // The current row group block-size is the block-origin of the 1st row + // we are about to calculate a block-size for. + WritingMode wm = aReflowInput.GetWritingMode(); + nsSize containerSize; // actual value is unimportant as we're initially + // computing sizes, not physical positions + nscoord startRowGroupBSize = + startRowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm); + + int32_t numRows = + GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex()); + // Collect the current bsize of each row. + if (numRows <= 0) return; + + AutoTArray<RowInfo, 32> rowInfo; + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + rowInfo.AppendElements(numRows); + + bool hasRowSpanningCell = false; + nscoord bSizeOfRows = 0; + nscoord bSizeOfUnStyledRows = 0; + // Get the bsize of each row without considering rowspans. This will be the + // max of the largest desired bsize of each cell, the largest style bsize of + // each cell, the style bsize of the row. + nscoord pctBSizeBasis = GetBSizeBasis(aReflowInput); + int32_t + rowIndex; // the index in rowInfo, not among the rows in the row group + nsTableRowFrame* rowFrame; + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; + rowFrame = rowFrame->GetNextRow(), rowIndex++) { + nscoord nonPctBSize = rowFrame->GetContentBSize(); + if (isPaginated) { + nonPctBSize = std::max(nonPctBSize, rowFrame->BSize(wm)); + } + if (!rowFrame->GetPrevInFlow()) { + if (rowFrame->HasPctBSize()) { + rowInfo[rowIndex].hasPctBSize = true; + rowInfo[rowIndex].pctBSize = rowFrame->GetInitialBSize(pctBSizeBasis); + } + rowInfo[rowIndex].hasStyleBSize = rowFrame->HasStyleBSize(); + nonPctBSize = std::max(nonPctBSize, rowFrame->GetFixedBSize()); + } + UpdateBSizes(rowInfo[rowIndex], nonPctBSize, bSizeOfRows, + bSizeOfUnStyledRows); + + if (!rowInfo[rowIndex].hasStyleBSize) { + if (isPaginated || + tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) { + rowInfo[rowIndex].isSpecial = true; + // iteratate the row's cell frames to see if any do not have rowspan > 1 + nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); + while (cellFrame) { + int32_t rowSpan = tableFrame->GetEffectiveRowSpan( + rowIndex + startRowIndex, *cellFrame); + if (1 == rowSpan) { + rowInfo[rowIndex].isSpecial = false; + break; + } + cellFrame = cellFrame->GetNextCell(); + } + } + } + // See if a cell spans into the row. If so we'll have to do the next step + if (!hasRowSpanningCell) { + if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) { + hasRowSpanningCell = true; + } + } + } + + if (hasRowSpanningCell) { + // Get the bsize of cells with rowspans and allocate any extra space to the + // rows they span iteratate the child frames and process the row frames + // among them + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; + rowFrame = rowFrame->GetNextRow(), rowIndex++) { + // See if the row has an originating cell with rowspan > 1. We cannot + // determine this for a row in a continued row group by calling + // RowHasSpanningCells, because the row's fif may not have any originating + // cells yet the row may have a continued cell which originates in it. + if (GetPrevInFlow() || tableFrame->RowHasSpanningCells( + startRowIndex + rowIndex, numEffCols)) { + nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); + // iteratate the row's cell frames + while (cellFrame) { + const nscoord rowSpacing = + tableFrame->GetRowSpacing(startRowIndex + rowIndex); + int32_t rowSpan = tableFrame->GetEffectiveRowSpan( + rowIndex + startRowIndex, *cellFrame); + if ((rowIndex + rowSpan) > numRows) { + // there might be rows pushed already to the nextInFlow + rowSpan = numRows - rowIndex; + } + if (rowSpan > 1) { // a cell with rowspan > 1, determine the bsize of + // the rows it spans + nscoord bsizeOfRowsSpanned = 0; + nscoord bsizeOfUnStyledRowsSpanned = 0; + nscoord numSpecialRowsSpanned = 0; + nscoord cellSpacingTotal = 0; + int32_t spanX; + for (spanX = 0; spanX < rowSpan; spanX++) { + bsizeOfRowsSpanned += rowInfo[rowIndex + spanX].bSize; + if (!rowInfo[rowIndex + spanX].hasStyleBSize) { + bsizeOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].bSize; + } + if (0 != spanX) { + cellSpacingTotal += rowSpacing; + } + if (rowInfo[rowIndex + spanX].isSpecial) { + numSpecialRowsSpanned++; + } + } + nscoord bsizeOfAreaSpanned = bsizeOfRowsSpanned + cellSpacingTotal; + // get the bsize of the cell + LogicalSize cellFrameSize = cellFrame->GetLogicalSize(wm); + LogicalSize cellDesSize = cellFrame->GetDesiredSize(); + cellDesSize.BSize(wm) = rowFrame->CalcCellActualBSize( + cellFrame, cellDesSize.BSize(wm), wm); + cellFrameSize.BSize(wm) = cellDesSize.BSize(wm); + if (cellFrame->HasVerticalAlignBaseline()) { + // to ensure that a spanning cell with a long descender doesn't + // collide with the next row, we need to take into account the + // shift that will be done to align the cell on the baseline of + // the row. + cellFrameSize.BSize(wm) += + rowFrame->GetMaxCellAscent() - cellFrame->GetCellBaseline(); + } + + if (bsizeOfAreaSpanned < cellFrameSize.BSize(wm)) { + // the cell's bsize is larger than the available space of the rows + // it spans so distribute the excess bsize to the rows affected + nscoord extra = cellFrameSize.BSize(wm) - bsizeOfAreaSpanned; + nscoord extraUsed = 0; + if (0 == numSpecialRowsSpanned) { + // NS_ASSERTION(bsizeOfRowsSpanned > 0, "invalid row span + // situation"); + bool haveUnStyledRowsSpanned = (bsizeOfUnStyledRowsSpanned > 0); + nscoord divisor = (haveUnStyledRowsSpanned) + ? bsizeOfUnStyledRowsSpanned + : bsizeOfRowsSpanned; + if (divisor > 0) { + for (spanX = rowSpan - 1; spanX >= 0; spanX--) { + if (!haveUnStyledRowsSpanned || + !rowInfo[rowIndex + spanX].hasStyleBSize) { + // The amount of additional space each row gets is + // proportional to its bsize + float percent = ((float)rowInfo[rowIndex + spanX].bSize) / + ((float)divisor); + + // give rows their percentage, except for the first row + // which gets the remainder + nscoord extraForRow = + (0 == spanX) + ? extra - extraUsed + : NSToCoordRound(((float)(extra)) * percent); + extraForRow = std::min(extraForRow, extra - extraUsed); + // update the row bsize + UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, + bSizeOfRows, bSizeOfUnStyledRows); + extraUsed += extraForRow; + if (extraUsed >= extra) { + NS_ASSERTION((extraUsed == extra), + "invalid row bsize calculation"); + break; + } + } + } + } else { + // put everything in the last row + UpdateBSizes(rowInfo[rowIndex + rowSpan - 1], extra, + bSizeOfRows, bSizeOfUnStyledRows); + } + } else { + // give the extra to the special rows + nscoord numSpecialRowsAllocated = 0; + for (spanX = rowSpan - 1; spanX >= 0; spanX--) { + if (rowInfo[rowIndex + spanX].isSpecial) { + // The amount of additional space each degenerate row gets + // is proportional to the number of them + float percent = 1.0f / ((float)numSpecialRowsSpanned); + + // give rows their percentage, except for the first row + // which gets the remainder + nscoord extraForRow = + (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated) + ? extra - extraUsed + : NSToCoordRound(((float)(extra)) * percent); + extraForRow = std::min(extraForRow, extra - extraUsed); + // update the row bsize + UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, + bSizeOfRows, bSizeOfUnStyledRows); + extraUsed += extraForRow; + if (extraUsed >= extra) { + NS_ASSERTION((extraUsed == extra), + "invalid row bsize calculation"); + break; + } + } + } + } + } + } // if (rowSpan > 1) + cellFrame = cellFrame->GetNextCell(); + } // while (cellFrame) + } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) { + } // while (rowFrame) + } + + // pct bsize rows have already got their content bsizes. + // Give them their pct bsizes up to pctBSizeBasis + nscoord extra = pctBSizeBasis - bSizeOfRows; + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0); + rowFrame = rowFrame->GetNextRow(), rowIndex++) { + RowInfo& rInfo = rowInfo[rowIndex]; + if (rInfo.hasPctBSize) { + nscoord rowExtra = + (rInfo.pctBSize > rInfo.bSize) ? rInfo.pctBSize - rInfo.bSize : 0; + rowExtra = std::min(rowExtra, extra); + UpdateBSizes(rInfo, rowExtra, bSizeOfRows, bSizeOfUnStyledRows); + extra -= rowExtra; + } + } + + bool styleBSizeAllocation = false; + nscoord rowGroupBSize = startRowGroupBSize + bSizeOfRows + + tableFrame->GetRowSpacing(0, numRows - 1); + // if we have a style bsize, allocate the extra bsize to unconstrained rows + if ((aReflowInput.ComputedBSize() > rowGroupBSize) && + (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize())) { + nscoord extraComputedBSize = aReflowInput.ComputedBSize() - rowGroupBSize; + nscoord extraUsed = 0; + bool haveUnStyledRows = (bSizeOfUnStyledRows > 0); + nscoord divisor = (haveUnStyledRows) ? bSizeOfUnStyledRows : bSizeOfRows; + if (divisor > 0) { + styleBSizeAllocation = true; + for (rowIndex = 0; rowIndex < numRows; rowIndex++) { + if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleBSize) { + // The amount of additional space each row gets is based on the + // percentage of space it occupies + float percent = ((float)rowInfo[rowIndex].bSize) / ((float)divisor); + // give rows their percentage, except for the last row which gets the + // remainder + nscoord extraForRow = + (numRows - 1 == rowIndex) + ? extraComputedBSize - extraUsed + : NSToCoordRound(((float)extraComputedBSize) * percent); + extraForRow = std::min(extraForRow, extraComputedBSize - extraUsed); + // update the row bsize + UpdateBSizes(rowInfo[rowIndex], extraForRow, bSizeOfRows, + bSizeOfUnStyledRows); + extraUsed += extraForRow; + if (extraUsed >= extraComputedBSize) { + NS_ASSERTION((extraUsed == extraComputedBSize), + "invalid row bsize calculation"); + break; + } + } + } + } + rowGroupBSize = aReflowInput.ComputedBSize(); + } + + if (wm.IsVertical()) { + // we need the correct containerSize below for block positioning in + // vertical-rl writing mode + containerSize.width = rowGroupBSize; + } + + nscoord bOrigin = startRowGroupBSize; + // update the rows with their (potentially) new bsizes + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; + rowFrame = rowFrame->GetNextRow(), rowIndex++) { + nsRect rowBounds = rowFrame->GetRect(); + LogicalSize rowBoundsSize(wm, rowBounds.Size()); + nsRect rowInkOverflow = rowFrame->InkOverflowRect(); + nscoord deltaB = + bOrigin - rowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm); + + nscoord rowBSize = + (rowInfo[rowIndex].bSize > 0) ? rowInfo[rowIndex].bSize : 0; + + if (deltaB != 0 || (rowBSize != rowBoundsSize.BSize(wm))) { + // Resize/move the row to its final size and position + if (deltaB != 0) { + rowFrame->InvalidateFrameSubtree(); + } + + rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, deltaB)); + rowFrame->SetSize(LogicalSize(wm, rowBoundsSize.ISize(wm), rowBSize)); + + nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowInkOverflow, + false); + + if (deltaB != 0) { + nsTableFrame::RePositionViews(rowFrame); + // XXXbz we don't need to update our overflow area? + } + } + bOrigin += rowBSize + tableFrame->GetRowSpacing(startRowIndex + rowIndex); + } + + if (isPaginated && styleBSizeAllocation) { + // since the row group has a style bsize, cache the row bsizes, + // so next in flows can honor them + CacheRowBSizesForPrinting(GetFirstRow(), wm); + } + + DidResizeRows(aDesiredSize); + + aDesiredSize.BSize(wm) = rowGroupBSize; // Adjust our desired size +} + +nscoord nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aBTotalOffset, + nscoord aISize, + WritingMode aWM) { + nsTableFrame* tableFrame = GetTableFrame(); + nsSize containerSize = tableFrame->GetSize(); + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; + if (collapseGroup) { + tableFrame->SetNeedToCollapse(true); + } + + OverflowAreas overflow; + + nsTableRowFrame* rowFrame = GetFirstRow(); + bool didCollapse = false; + nscoord bGroupOffset = 0; + while (rowFrame) { + bGroupOffset += rowFrame->CollapseRowIfNecessary( + bGroupOffset, aISize, collapseGroup, didCollapse); + ConsiderChildOverflow(overflow, rowFrame); + rowFrame = rowFrame->GetNextRow(); + } + + LogicalRect groupRect = GetLogicalRect(aWM, containerSize); + nsRect oldGroupRect = GetRect(); + nsRect oldGroupInkOverflow = InkOverflowRect(); + + groupRect.BSize(aWM) -= bGroupOffset; + if (didCollapse) { + // add back the cellspacing between rowgroups + groupRect.BSize(aWM) += + tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount()); + } + + groupRect.BStart(aWM) -= aBTotalOffset; + groupRect.ISize(aWM) = aISize; + + if (aBTotalOffset != 0) { + InvalidateFrameSubtree(); + } + + SetRect(aWM, groupRect, containerSize); + overflow.UnionAllWith( + nsRect(0, 0, groupRect.Width(aWM), groupRect.Height(aWM))); + FinishAndStoreOverflow(overflow, groupRect.Size(aWM).GetPhysicalSize(aWM)); + nsTableFrame::RePositionViews(this); + nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupInkOverflow, + false); + + return bGroupOffset; +} + +nsTableRowFrame* nsTableRowGroupFrame::CreateContinuingRowFrame( + nsIFrame* aRowFrame) { + // Create the continuing frame which will create continuing cell frames. + auto* contRowFrame = static_cast<nsTableRowFrame*>( + PresShell()->FrameConstructor()->CreateContinuingFrame(aRowFrame, this)); + + // Add the continuing row frame to the child list. + mFrames.InsertFrame(nullptr, aRowFrame, contRowFrame); + + // Push the continuing row frame and the frames that follow. + // This needs to match `UndoContinuedRow`. + PushChildrenToOverflow(contRowFrame, aRowFrame); + + return contRowFrame; +} + +// Reflow the cells with rowspan > 1 which originate between aFirstRow +// and end on or after aLastRow. aFirstTruncatedRow is the highest row on the +// page that contains a cell which cannot split on this page +void nsTableRowGroupFrame::SplitSpanningCells( + nsPresContext* aPresContext, const ReflowInput& aReflowInput, + nsTableFrame* aTable, nsTableRowFrame* aFirstRow, nsTableRowFrame* aLastRow, + bool aFirstRowIsTopOfPage, nscoord aSpanningRowBEnd, + const nsSize& aContainerSize, nsTableRowFrame*& aContRow, + nsTableRowFrame*& aFirstTruncatedRow, nscoord& aDesiredBSize) { + NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes"); + aFirstTruncatedRow = nullptr; + aDesiredBSize = 0; + + const WritingMode wm = aReflowInput.GetWritingMode(); + const bool borderCollapse = aTable->IsBorderCollapse(); + int32_t lastRowIndex = aLastRow->GetRowIndex(); + bool wasLast = false; + bool haveRowSpan = false; + // Iterate the rows between aFirstRow and aLastRow + for (nsTableRowFrame* row = aFirstRow; !wasLast; row = row->GetNextRow()) { + wasLast = (row == aLastRow); + int32_t rowIndex = row->GetRowIndex(); + const LogicalRect rowRect = row->GetLogicalNormalRect(wm, aContainerSize); + // Iterate the cells looking for those that have rowspan > 1 + for (nsTableCellFrame* cell = row->GetFirstCell(); cell; + cell = cell->GetNextCell()) { + int32_t rowSpan = aTable->GetEffectiveRowSpan(rowIndex, *cell); + // Only reflow rowspan > 1 cells which span aLastRow. Those which don't + // span aLastRow were reflowed correctly during the unconstrained bsize + // reflow. + if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) { + haveRowSpan = true; + nsReflowStatus status; + // Ask the row to reflow the cell to the bsize of all the rows it spans + // up through aLastRow cellAvailBSize is the space between the row group + // start and the end of the page + const nscoord cellAvailBSize = aSpanningRowBEnd - rowRect.BStart(wm); + NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?"); + bool isTopOfPage = (row == aFirstRow) && aFirstRowIsTopOfPage; + + LogicalSize rowAvailSize( + wm, aReflowInput.AvailableISize(), + std::max(aReflowInput.AvailableBSize() - rowRect.BStart(wm), 0)); + // Don't let the available block-size exceed what CalculateRowBSizes set + // for it. + rowAvailSize.BSize(wm) = + std::min(rowAvailSize.BSize(wm), rowRect.BSize(wm)); + ReflowInput rowReflowInput( + aPresContext, aReflowInput, row, + rowAvailSize.ConvertTo(row->GetWritingMode(), wm), Nothing(), + ReflowInput::InitFlag::CallerWillInit); + InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput); + rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page + + nscoord cellBSize = + row->ReflowCellFrame(aPresContext, rowReflowInput, isTopOfPage, + cell, cellAvailBSize, status); + aDesiredBSize = std::max(aDesiredBSize, rowRect.BStart(wm) + cellBSize); + if (status.IsComplete()) { + if (cellBSize > cellAvailBSize) { + aFirstTruncatedRow = row; + if ((row != aFirstRow) || !aFirstRowIsTopOfPage) { + // return now, since we will be getting another reflow after + // either (1) row is moved to the next page or (2) the row group + // is moved to the next page + return; + } + } + } else { + if (!aContRow) { + aContRow = CreateContinuingRowFrame(aLastRow); + } + if (aContRow) { + if (row != aLastRow) { + // aContRow needs a continuation for cell, since cell spanned into + // aLastRow but does not originate there + nsTableCellFrame* contCell = static_cast<nsTableCellFrame*>( + PresShell()->FrameConstructor()->CreateContinuingFrame( + cell, aLastRow)); + uint32_t colIndex = cell->ColIndex(); + aContRow->InsertCellFrame(contCell, colIndex); + } + } + } + } + } + } + if (!haveRowSpan) { + aDesiredBSize = aLastRow->GetLogicalNormalRect(wm, aContainerSize).BEnd(wm); + } +} + +// Remove the next-in-flow of the row, its cells and their cell blocks. This +// is necessary in case the row doesn't need a continuation later on or needs +// a continuation which doesn't have the same number of cells that now exist. +void nsTableRowGroupFrame::UndoContinuedRow(nsPresContext* aPresContext, + nsTableRowFrame* aRow) { + if (!aRow) return; // allow null aRow to avoid callers doing null checks + + // rowBefore was the prev-sibling of aRow's next-sibling before aRow was + // created + nsTableRowFrame* rowBefore = (nsTableRowFrame*)aRow->GetPrevInFlow(); + MOZ_ASSERT(mFrames.ContainsFrame(rowBefore), + "rowBefore not in our frame list?"); + + // Needs to match `CreateContinuingRowFrame` - we're assuming that continued + // frames always go into overflow frames list. + AutoFrameListPtr overflows(aPresContext, StealOverflowFrames()); + if (!rowBefore || !overflows || overflows->IsEmpty() || + overflows->FirstChild() != aRow) { + NS_ERROR("invalid continued row"); + return; + } + + DestroyContext context(aPresContext->PresShell()); + // Destroy aRow, its cells, and their cell blocks. Cell blocks that have split + // will not have reflowed yet to pick up content from any overflow lines. + overflows->DestroyFrame(context, aRow); + + // Put the overflow rows into our child list + if (!overflows->IsEmpty()) { + mFrames.InsertFrames(nullptr, rowBefore, std::move(*overflows)); + } +} + +void nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame, + nsReflowStatus& aStatus, + bool aRowForcedPageBreak) { + MOZ_ASSERT(aPresContext->IsPaginated(), + "SplitRowGroup currently supports only paged media"); + + const WritingMode wm = aReflowInput.GetWritingMode(); + nsTableRowFrame* prevRowFrame = nullptr; + aDesiredSize.BSize(wm) = 0; + aDesiredSize.SetOverflowAreasToDesiredBounds(); + + const nscoord availISize = aReflowInput.AvailableISize(); + const nscoord availBSize = aReflowInput.AvailableBSize(); + const nsSize containerSize = + aReflowInput.ComputedSizeAsContainerIfConstrained(); + const bool borderCollapse = aTableFrame->IsBorderCollapse(); + + const nscoord pageBSize = + LogicalSize(wm, aPresContext->GetPageSize()).BSize(wm); + NS_ASSERTION(pageBSize != NS_UNCONSTRAINEDSIZE, + "The table shouldn't be split when there should be space"); + + bool isTopOfPage = aReflowInput.mFlags.mIsTopOfPage; + nsTableRowFrame* firstRowThisPage = GetFirstRow(); + + // Need to dirty the table's geometry, or else the row might skip + // reflowing its cell as an optimization. + aTableFrame->SetGeometryDirty(); + + // Walk each of the row frames looking for the first row frame that doesn't + // fit in the available space + for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame; + rowFrame = rowFrame->GetNextRow()) { + bool rowIsOnPage = true; + const nscoord rowSpacing = + aTableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + const LogicalRect rowRect = + rowFrame->GetLogicalNormalRect(wm, containerSize); + // See if the row fits on this page + if (rowRect.BEnd(wm) > availBSize) { + nsTableRowFrame* contRow = nullptr; + // Reflow the row in the availabe space and have it split if it is the 1st + // row (on the page) or there is at least 5% of the current page available + // XXX this 5% should be made a preference + if (!prevRowFrame || + (availBSize - aDesiredSize.BSize(wm) > pageBSize / 20)) { + LogicalSize availSize(wm, availISize, + std::max(availBSize - rowRect.BStart(wm), 0)); + // Don't let the available block-size exceed what CalculateRowBSizes set + // for it. + availSize.BSize(wm) = std::min(availSize.BSize(wm), rowRect.BSize(wm)); + + ReflowInput rowReflowInput( + aPresContext, aReflowInput, rowFrame, + availSize.ConvertTo(rowFrame->GetWritingMode(), wm), Nothing(), + ReflowInput::InitFlag::CallerWillInit); + + InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput); + rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page + ReflowOutput rowMetrics(aReflowInput); + + // Get the old size before we reflow. + nsRect oldRowRect = rowFrame->GetRect(); + nsRect oldRowInkOverflow = rowFrame->InkOverflowRect(); + + // Reflow the cell with the constrained bsize. A cell with rowspan >1 + // will get this reflow later during SplitSpanningCells. + // + // Note: We just pass dummy aPos and aContainerSize since we are not + // moving the row frame. + const LogicalPoint dummyPos(wm); + const nsSize dummyContainerSize; + ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput, wm, + dummyPos, dummyContainerSize, ReflowChildFlags::NoMoveFrame, + aStatus); + FinishReflowChild(rowFrame, aPresContext, rowMetrics, &rowReflowInput, + wm, dummyPos, dummyContainerSize, + ReflowChildFlags::NoMoveFrame); + rowFrame->DidResize(); + + if (!aRowForcedPageBreak && !aStatus.IsFullyComplete() && + ShouldAvoidBreakInside(aReflowInput)) { + aStatus.SetInlineLineBreakBeforeAndReset(); + break; + } + + nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect, + oldRowInkOverflow, false); + + if (aStatus.IsIncomplete()) { + // The row frame is incomplete and all of the rowspan 1 cells' block + // frames split + if ((rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize()) || + isTopOfPage) { + // The row stays on this page because either it split ok or we're on + // the top of page. If top of page and the block-size exceeded the + // avail block-size, then there will be data loss. + NS_ASSERTION( + rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize(), + "Data loss - incomplete row needed more block-size than " + "available, on top of page!"); + contRow = CreateContinuingRowFrame(rowFrame); + aDesiredSize.BSize(wm) += rowMetrics.BSize(wm); + if (prevRowFrame) { + aDesiredSize.BSize(wm) += rowSpacing; + } + } else { + // Put the row on the next page to give it more block-size. + rowIsOnPage = false; + } + } else { + // The row frame is complete because either (1) its minimum block-size + // is greater than the available block-size we gave it, or (2) it may + // have been given a larger block-size through style than its content, + // or (3) it contains a rowspan >1 cell which hasn't been reflowed + // with a constrained block-size yet (we will find out when + // SplitSpanningCells is called below) + if (rowMetrics.BSize(wm) > availSize.BSize(wm) || + (aStatus.IsInlineBreakBefore() && !aRowForcedPageBreak)) { + // cases (1) and (2) + if (isTopOfPage) { + // We're on top of the page, so keep the row on this page. There + // will be data loss. Push the row frame that follows + nsTableRowFrame* nextRowFrame = rowFrame->GetNextRow(); + if (nextRowFrame) { + aStatus.Reset(); + aStatus.SetIncomplete(); + } + aDesiredSize.BSize(wm) += rowMetrics.BSize(wm); + if (prevRowFrame) { + aDesiredSize.BSize(wm) += rowSpacing; + } + NS_WARNING( + "Data loss - complete row needed more block-size than " + "available, on top of page"); + } else { + // We're not on top of the page, so put the row on the next page + // to give it more block-size. + rowIsOnPage = false; + } + } + } + } else { + // Put the row on the next page to give it more block-size. + rowIsOnPage = false; + } + + nsTableRowFrame* lastRowThisPage = rowFrame; + nscoord spanningRowBEnd = availBSize; + if (!rowIsOnPage) { + NS_ASSERTION(!contRow, + "We should not have created a continuation if none of " + "this row fits"); + if (!prevRowFrame || + (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput))) { + aStatus.SetInlineLineBreakBeforeAndReset(); + break; + } + spanningRowBEnd = + prevRowFrame->GetLogicalNormalRect(wm, containerSize).BEnd(wm); + lastRowThisPage = prevRowFrame; + aStatus.Reset(); + aStatus.SetIncomplete(); + } + + // reflow the cells with rowspan >1 that occur on the page + nsTableRowFrame* firstTruncatedRow; + nscoord bMost; + SplitSpanningCells(aPresContext, aReflowInput, aTableFrame, + firstRowThisPage, lastRowThisPage, + aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd, + containerSize, contRow, firstTruncatedRow, bMost); + if (firstTruncatedRow) { + // A rowspan >1 cell did not fit (and could not split) in the space we + // gave it + if (firstTruncatedRow == firstRowThisPage) { + if (aReflowInput.mFlags.mIsTopOfPage) { + NS_WARNING("data loss in a row spanned cell"); + } else { + // We can't push children, so let our parent reflow us again with + // more space + aDesiredSize.BSize(wm) = rowRect.BEnd(wm); + aStatus.Reset(); + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } else { + // Try to put firstTruncateRow on the next page + nsTableRowFrame* rowBefore = firstTruncatedRow->GetPrevRow(); + const nscoord oldSpanningRowBEnd = spanningRowBEnd; + spanningRowBEnd = + rowBefore->GetLogicalNormalRect(wm, containerSize).BEnd(wm); + + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + nsTableRowFrame* oldLastRowThisPage = lastRowThisPage; + lastRowThisPage = rowBefore; + aStatus.Reset(); + aStatus.SetIncomplete(); + + // Call SplitSpanningCells again with rowBefore as the last row on the + // page + SplitSpanningCells(aPresContext, aReflowInput, aTableFrame, + firstRowThisPage, rowBefore, + aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd, + containerSize, contRow, firstTruncatedRow, + aDesiredSize.BSize(wm)); + if (firstTruncatedRow) { + if (aReflowInput.mFlags.mIsTopOfPage) { + // We were better off with the 1st call to SplitSpanningCells, do + // it again + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + lastRowThisPage = oldLastRowThisPage; + spanningRowBEnd = oldSpanningRowBEnd; + SplitSpanningCells(aPresContext, aReflowInput, aTableFrame, + firstRowThisPage, lastRowThisPage, + aReflowInput.mFlags.mIsTopOfPage, + spanningRowBEnd, containerSize, contRow, + firstTruncatedRow, aDesiredSize.BSize(wm)); + NS_WARNING("data loss in a row spanned cell"); + } else { + // Let our parent reflow us again with more space + aDesiredSize.BSize(wm) = rowRect.BEnd(wm); + aStatus.Reset(); + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } + } + } else { + aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bMost); + if (contRow) { + aStatus.Reset(); + aStatus.SetIncomplete(); + } + } + if (aStatus.IsIncomplete() && !contRow) { + if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) { + PushChildrenToOverflow(nextRow, lastRowThisPage); + } + } else if (aStatus.IsComplete() && lastRowThisPage) { + // Our size from the unconstrained reflow exceeded the constrained + // available space but our size in the constrained reflow is Complete. + // This can happen when a non-zero block-end margin is suppressed in + // nsBlockFrame::ComputeFinalSize. + if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) { + aStatus.Reset(); + aStatus.SetIncomplete(); + PushChildrenToOverflow(nextRow, lastRowThisPage); + } + } + break; + } + aDesiredSize.BSize(wm) = rowRect.BEnd(wm); + prevRowFrame = rowFrame; + // see if there is a page break after the row + nsTableRowFrame* nextRow = rowFrame->GetNextRow(); + if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) { + PushChildrenToOverflow(nextRow, rowFrame); + aStatus.Reset(); + aStatus.SetIncomplete(); + break; + } + // After the 1st row that has a block-size, we can't be on top of the page + // anymore. + isTopOfPage = isTopOfPage && rowRect.BEnd(wm) == 0; + } +} + +/** Layout the entire row group. + * This method stacks rows vertically according to HTML 4.0 rules. + * Rows are responsible for layout of their children. + */ +void nsTableRowGroupFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableRowGroupFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + // Row geometry may be going to change so we need to invalidate any row + // cursor. + ClearRowCursor(); + + // see if a special bsize reflow needs to occur due to having a pct bsize + nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput); + + nsTableFrame* tableFrame = GetTableFrame(); + TableRowGroupReflowInput state(aReflowInput); + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; + if (collapseGroup) { + tableFrame->SetNeedToCollapse(true); + } + + // Check for an overflow list + MoveOverflowToChildList(); + + // Reflow the existing frames. + bool splitDueToPageBreak = false; + ReflowChildren(aPresContext, aDesiredSize, state, aStatus, + &splitDueToPageBreak); + + // See if all the frames fit. Do not try to split anything if we're + // not paginated ... we can't split across columns yet. + WritingMode wm = aReflowInput.GetWritingMode(); + if (aReflowInput.mFlags.mTableIsSplittable && + aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && + (aStatus.IsIncomplete() || splitDueToPageBreak || + aDesiredSize.BSize(wm) > aReflowInput.AvailableBSize())) { + // Nope, find a place to split the row group + auto& mutableRIFlags = const_cast<ReflowInput::Flags&>(aReflowInput.mFlags); + const bool savedSpecialBSizeReflow = mutableRIFlags.mSpecialBSizeReflow; + mutableRIFlags.mSpecialBSizeReflow = false; + + SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus, + splitDueToPageBreak); + + mutableRIFlags.mSpecialBSizeReflow = savedSpecialBSizeReflow; + } + + // XXXmats The following is just bogus. We leave it here for now because + // ReflowChildren should pull up rows from our next-in-flow before returning + // a Complete status, but doesn't (bug 804888). + if (GetNextInFlow() && GetNextInFlow()->PrincipalChildList().FirstChild()) { + aStatus.SetIncomplete(); + } + + SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) && + (aReflowInput.ComputedBSize() > 0)); + + // Just set our isize to what was available. + // The table will calculate the isize and not use our value. + aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); + + aDesiredSize.UnionOverflowAreasWithDesiredBounds(); + + // If our parent is in initial reflow, it'll handle invalidating our + // entire overflow rect. + if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && + aDesiredSize.Size(wm) != GetLogicalSize(wm)) { + InvalidateFrame(); + } + + FinishAndStoreOverflow(&aDesiredSize); + + // Any absolutely-positioned children will get reflowed in + // nsIFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); +} + +bool nsTableRowGroupFrame::ComputeCustomOverflow( + OverflowAreas& aOverflowAreas) { + // Row cursor invariants depend on the ink overflow area of the rows, + // which may have changed, so we need to clear the cursor now. + ClearRowCursor(); + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +/* virtual */ +void nsTableRowGroupFrame::DidSetComputedStyle( + ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle); + + if (!aOldComputedStyle) { + return; // avoid the following on init + } + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { + TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(), + GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void nsTableRowGroupFrame::AppendFrames(ChildListID aListID, + nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + + DrainSelfOverflowList(); // ensure the last frame is in mFrames + ClearRowCursor(); + + // collect the new row frames in an array + // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here. + AutoTArray<nsTableRowFrame*, 8> rows; + for (nsIFrame* f : aFrameList) { + nsTableRowFrame* rowFrame = do_QueryFrame(f); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION( + mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + rows.AppendElement(rowFrame); + } + } + + int32_t rowIndex = GetRowCount(); + // Append the frames to the sibling chain + mFrames.AppendFrames(nullptr, std::move(aFrameList)); + + if (rows.Length() > 0) { + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->AppendRows(this, rowIndex, rows); + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void nsTableRowGroupFrame::InsertFrames( + ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + + DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames + ClearRowCursor(); + + // collect the new row frames in an array + // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here. + nsTableFrame* tableFrame = GetTableFrame(); + nsTArray<nsTableRowFrame*> rows; + bool gotFirstRow = false; + for (nsIFrame* f : aFrameList) { + nsTableRowFrame* rowFrame = do_QueryFrame(f); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION( + mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + rows.AppendElement(rowFrame); + if (!gotFirstRow) { + rowFrame->SetFirstInserted(true); + gotFirstRow = true; + tableFrame->SetRowInserted(true); + } + } + } + + int32_t startRowIndex = GetStartRowIndex(); + // Insert the frames in the sibling chain + mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); + + int32_t numRows = rows.Length(); + if (numRows > 0) { + nsTableRowFrame* prevRow = + (nsTableRowFrame*)nsTableFrame::GetFrameAtOrBefore( + this, aPrevFrame, LayoutFrameType::TableRow); + int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex; + tableFrame->InsertRows(this, rows, rowIndex, true); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void nsTableRowGroupFrame::RemoveFrame(DestroyContext& aContext, + ChildListID aListID, + nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + + ClearRowCursor(); + + // XXX why are we doing the QI stuff? There shouldn't be any non-rows here. + nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame); + if (rowFrame) { + nsTableFrame* tableFrame = GetTableFrame(); + // remove the rows from the table (and flag a rebalance) + tableFrame->RemoveRows(*rowFrame, 1, true); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } + mFrames.DestroyFrame(aContext, aOldFrame); +} + +/* virtual */ +nsMargin nsTableRowGroupFrame::GetUsedMargin() const { + return nsMargin(0, 0, 0, 0); +} + +/* virtual */ +nsMargin nsTableRowGroupFrame::GetUsedBorder() const { + return nsMargin(0, 0, 0, 0); +} + +/* virtual */ +nsMargin nsTableRowGroupFrame::GetUsedPadding() const { + return nsMargin(0, 0, 0, 0); +} + +nscoord nsTableRowGroupFrame::GetBSizeBasis(const ReflowInput& aReflowInput) { + nscoord result = 0; + nsTableFrame* tableFrame = GetTableFrame(); + int32_t startRowIndex = GetStartRowIndex(); + if ((aReflowInput.ComputedBSize() > 0) && + (aReflowInput.ComputedBSize() < NS_UNCONSTRAINEDSIZE)) { + nscoord cellSpacing = tableFrame->GetRowSpacing( + startRowIndex, + std::max(startRowIndex, startRowIndex + GetRowCount() - 1)); + result = aReflowInput.ComputedBSize() - cellSpacing; + } else { + const ReflowInput* parentRI = aReflowInput.mParentReflowInput; + if (parentRI && (tableFrame != parentRI->mFrame)) { + parentRI = parentRI->mParentReflowInput; + } + if (parentRI && (tableFrame == parentRI->mFrame) && + (parentRI->ComputedBSize() > 0) && + (parentRI->ComputedBSize() < NS_UNCONSTRAINEDSIZE)) { + nscoord cellSpacing = + tableFrame->GetRowSpacing(-1, tableFrame->GetRowCount()); + result = parentRI->ComputedBSize() - cellSpacing; + } + } + + return result; +} + +bool nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame, + nsTableRowFrame* aRowFrame) { + int32_t rowIndex = aRowFrame->GetRowIndex(); + + // It's a simple row frame if there are no cells that span into or + // across the row + int32_t numEffCols = aTableFrame->GetEffectiveColCount(); + if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) && + !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) { + return true; + } + + return false; +} + +/** find page break before the first row **/ +bool nsTableRowGroupFrame::HasInternalBreakBefore() const { + nsIFrame* firstChild = mFrames.FirstChild(); + if (!firstChild) return false; + return firstChild->StyleDisplay()->BreakBefore(); +} + +/** find page break after the last row **/ +bool nsTableRowGroupFrame::HasInternalBreakAfter() const { + nsIFrame* lastChild = mFrames.LastChild(); + if (!lastChild) return false; + return lastChild->StyleDisplay()->BreakAfter(); +} +/* ----- global methods ----- */ + +nsTableRowGroupFrame* NS_NewTableRowGroupFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsTableRowGroupFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableRowGroup"_ns, aResult); +} +#endif + +LogicalMargin nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) { + LogicalMargin border(aWM); + nsTableRowFrame* firstRowFrame = GetFirstRow(); + if (!firstRowFrame) { + return border; + } + nsTableRowFrame* lastRowFrame = firstRowFrame; + for (nsTableRowFrame* rowFrame = firstRowFrame->GetNextRow(); rowFrame; + rowFrame = rowFrame->GetNextRow()) { + lastRowFrame = rowFrame; + } + border.BStart(aWM) = PresContext()->DevPixelsToAppUnits( + firstRowFrame->GetBStartBCBorderWidth()); + border.BEnd(aWM) = + PresContext()->DevPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth()); + return border; +} + +// nsILineIterator methods +int32_t nsTableRowGroupFrame::GetNumLines() const { return GetRowCount(); } + +bool nsTableRowGroupFrame::IsLineIteratorFlowRTL() { + return StyleDirection::Rtl == GetTableFrame()->StyleVisibility()->mDirection; +} + +Result<nsILineIterator::LineInfo, nsresult> nsTableRowGroupFrame::GetLine( + int32_t aLineNumber) { + if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) { + return Err(NS_ERROR_FAILURE); + } + LineInfo structure; + nsTableFrame* table = GetTableFrame(); + nsTableCellMap* cellMap = table->GetCellMap(); + aLineNumber += GetStartRowIndex(); + + structure.mNumFramesOnLine = + cellMap->GetNumCellsOriginatingInRow(aLineNumber); + if (structure.mNumFramesOnLine == 0) { + return structure; + } + int32_t colCount = table->GetColCount(); + for (int32_t i = 0; i < colCount; i++) { + CellData* data = cellMap->GetDataAt(aLineNumber, i); + if (data && data->IsOrig()) { + structure.mFirstFrameOnLine = (nsIFrame*)data->GetCellFrame(); + nsIFrame* parent = structure.mFirstFrameOnLine->GetParent(); + structure.mLineBounds = parent->GetRect(); + return structure; + } + } + MOZ_ASSERT_UNREACHABLE("cellmap is lying"); + return Err(NS_ERROR_FAILURE); +} + +int32_t nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame, + int32_t aStartLine) { + NS_ENSURE_TRUE(aFrame, -1); + + nsTableRowFrame* rowFrame = do_QueryFrame(aFrame); + if (MOZ_UNLIKELY(!rowFrame)) { + // When we do not have valid table structure in the DOM tree, somebody wants + // to check the line number with an out-of-flow child of this frame because + // its parent frame is set to this frame. Otherwise, the caller must have + // a bug. + MOZ_ASSERT(aFrame->GetParent() == this); + MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); + return -1; + } + + int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex(); + + return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1; +} + +NS_IMETHODIMP +nsTableRowGroupFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered, + nsIFrame** aFirstVisual, + nsIFrame** aLastVisual) { + *aIsReordered = false; + *aFirstVisual = nullptr; + *aLastVisual = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos, + nsIFrame** aFrameFound, + bool* aPosIsBeforeFirstFrame, + bool* aPosIsAfterLastFrame) { + nsTableFrame* table = GetTableFrame(); + nsTableCellMap* cellMap = table->GetCellMap(); + + *aFrameFound = nullptr; + *aPosIsBeforeFirstFrame = true; + *aPosIsAfterLastFrame = false; + + aLineNumber += GetStartRowIndex(); + int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber); + if (numCells == 0) { + return NS_OK; + } + + nsIFrame* frame = nullptr; + int32_t colCount = table->GetColCount(); + for (int32_t i = 0; i < colCount; i++) { + CellData* data = cellMap->GetDataAt(aLineNumber, i); + if (data && data->IsOrig()) { + frame = (nsIFrame*)data->GetCellFrame(); + break; + } + } + NS_ASSERTION(frame, "cellmap is lying"); + bool isRTL = StyleDirection::Rtl == table->StyleVisibility()->mDirection; + + LineFrameFinder finder(aPos, table->GetSize(), table->GetWritingMode(), + isRTL); + + int32_t n = numCells; + while (n--) { + finder.Scan(frame); + if (finder.IsDone()) { + break; + } + frame = frame->GetNextSibling(); + } + finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame); + return NS_OK; +} + +// end nsLineIterator methods + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty, + nsTableRowGroupFrame::FrameCursorData) + +void nsTableRowGroupFrame::ClearRowCursor() { + if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { + return; + } + + RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); + RemoveProperty(RowCursorProperty()); +} + +nsTableRowGroupFrame::FrameCursorData* nsTableRowGroupFrame::SetupRowCursor() { + if (HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { + // We already have a valid row cursor. Don't waste time rebuilding it. + return nullptr; + } + + nsIFrame* f = mFrames.FirstChild(); + int32_t count; + for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) { + f = f->GetNextSibling(); + } + if (!f) { + // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother + return nullptr; + } + + FrameCursorData* data = new FrameCursorData(); + SetProperty(RowCursorProperty(), data); + AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); + return data; +} + +nsIFrame* nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, + nscoord* aOverflowAbove) { + if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { + return nullptr; + } + + FrameCursorData* property = GetProperty(RowCursorProperty()); + uint32_t cursorIndex = property->mCursorIndex; + uint32_t frameCount = property->mFrames.Length(); + if (cursorIndex >= frameCount) return nullptr; + nsIFrame* cursorFrame = property->mFrames[cursorIndex]; + + // The cursor's frame list excludes frames with empty overflow-area, so + // we don't need to check that here. + + // We use property->mOverflowBelow here instead of computing the frame's + // true overflowArea.YMost(), because it is essential for the thresholds + // to form a monotonically increasing sequence. Otherwise we would break + // encountering a row whose overflowArea.YMost() is <= aY but which has + // a row above it containing cell(s) that span to include aY. + while (cursorIndex > 0 && + cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) { + --cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + while (cursorIndex + 1 < frameCount && + cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) { + ++cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + + property->mCursorIndex = cursorIndex; + *aOverflowAbove = property->mOverflowAbove; + return cursorFrame; +} + +bool nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) { + // The cursor requires a monotonically increasing sequence in order to + // identify which rows can be skipped, and position:relative can move + // rows around such that the overflow areas don't provide this. + // We take the union of the overflow rect, and the frame's 'normal' position + // (excluding position:relative changes) and record the max difference between + // this combined overflow and the frame's rect. + nsRect positionedOverflowRect = aFrame->InkOverflowRect(); + nsPoint positionedToNormal = + aFrame->GetNormalPosition() - aFrame->GetPosition(); + nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal; + + nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect); + if (overflowRect.IsEmpty()) return true; + nscoord overflowAbove = -overflowRect.y; + nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height; + mOverflowAbove = std::max(mOverflowAbove, overflowAbove); + mOverflowBelow = std::max(mOverflowBelow, overflowBelow); + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + mFrames.AppendElement(aFrame); + return true; +} + +void nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); + if (GetTableFrame()->IsBorderCollapse()) { + const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); + GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), + aDisplayItemKey, rebuild); + } +} + +void nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, + aRebuildDisplayItems); + // If we have filters applied that would affects our bounds, then + // we get an inactive layer created and this is computed + // within FrameLayerBuilder + GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey, + aRebuildDisplayItems); +} |