diff options
Diffstat (limited to 'layout/tables/nsTableRowGroupFrame.cpp')
-rw-r--r-- | layout/tables/nsTableRowGroupFrame.cpp | 2019 |
1 files changed, 2019 insertions, 0 deletions
diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp new file mode 100644 index 000000000..60596f12b --- /dev/null +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -0,0 +1,2019 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsTableRowGroupFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsIContent.h" +#include "nsGkAtoms.h" +#include "nsIPresShell.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 { + const ReflowInput& reflowInput; // Our reflow state + + nsTableFrame* tableFrame; + + // The available size (computed from the parent) + mozilla::LogicalSize availSize; + + // Running block-offset + nscoord bCoord; + + TableRowGroupReflowInput(const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame) + : reflowInput(aReflowInput) + , tableFrame(aTableFrame) + , availSize(aReflowInput.GetWritingMode(), + aReflowInput.AvailableISize(), + aReflowInput.AvailableBSize()) + , bCoord(0) + { + } + + ~TableRowGroupReflowInput() {} +}; + +} // namespace mozilla + +nsTableRowGroupFrame::nsTableRowGroupFrame(nsStyleContext* aContext): + nsContainerFrame(aContext) +{ + SetRepeatable(false); +} + +nsTableRowGroupFrame::~nsTableRowGroupFrame() +{ +} + +void +nsTableRowGroupFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { + nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot); + } + + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +NS_QUERYFRAME_HEAD(nsTableRowGroupFrame) + NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +int32_t +nsTableRowGroupFrame::GetRowCount() +{ +#ifdef DEBUG + for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) { + NS_ASSERTION(e.get()->StyleDisplay()->mDisplay == + mozilla::StyleDisplay::TableRow, + "Unexpected display"); + NS_ASSERTION(e.get()->GetType() == nsGkAtoms::tableRowFrame, + "Unexpected frame type"); + } +#endif + + return mFrames.GetLength(); +} + +int32_t nsTableRowGroupFrame::GetStartRowIndex() +{ + int32_t result = -1; + if (mFrames.NotEmpty()) { + NS_ASSERTION(mFrames.FirstChild()->GetType() == nsGkAtoms::tableRowFrame, + "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); + } + } +} +nsresult +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"); + int32_t colIndex; + originalCellFrame->GetColIndex(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(); + } + + return NS_OK; +} + +/** + * We need a custom display item for table row backgrounds. This is only used + * when the table row is the root of a stacking context (e.g., has 'opacity'). + * Table row backgrounds can extend beyond the row frame bounds, when + * the row contains row-spanning cells. + */ +class nsDisplayTableRowGroupBackground : public nsDisplayTableItem { +public: + nsDisplayTableRowGroupBackground(nsDisplayListBuilder* aBuilder, + nsTableRowGroupFrame* aFrame) : + nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableRowGroupBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableRowGroupBackground() { + MOZ_COUNT_DTOR(nsDisplayTableRowGroupBackground); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + + NS_DISPLAY_DECL_NAME("TableRowGroupBackground", TYPE_TABLE_ROW_GROUP_BACKGROUND) +}; + +void +nsDisplayTableRowGroupBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + auto rgFrame = static_cast<nsTableRowGroupFrame*>(mFrame); + TableBackgroundPainter painter(rgFrame->GetTableFrame(), + TableBackgroundPainter::eOrigin_TableRowGroup, + mFrame->PresContext(), *aCtx, + mVisibleRect, ToReferenceFrame(), + aBuilder->GetBackgroundPaintFlags()); + + DrawResult result = painter.PaintRowGroup(rgFrame); + nsDisplayTableItemGeometry::UpdateDrawResult(this, result); +} + +// Handle the child-traversal part of DisplayGenericTablePart +static void +DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, + const nsRect& aDirtyRect, 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) ? + nullptr : f->GetFirstRowContaining(aDirtyRect.y, &overflowAbove); + + if (kid) { + // have a cursor, use it + while (kid) { + if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost() && + kid->GetNormalRect().y - overflowAbove >= aDirtyRect.YMost()) + break; + f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, 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, aDirtyRect, aLists); + + if (cursor) { + if (!cursor->AppendFrame(kid)) { + f->ClearRowCursor(); + return; + } + } + + kid = kid->GetNextSibling(); + } + if (cursor) { + cursor->FinishBuildingCursor(); + } +} + +void +nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsDisplayTableItem* item = nullptr; + if (IsVisibleInSelection(aBuilder)) { + bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext(); + if (isRoot) { + // This background is created regardless of whether this frame is + // visible or not. Visibility decisions are delegated to the + // table background painter. + item = new (aBuilder) nsDisplayTableRowGroupBackground(aBuilder, this); + aLists.BorderBackground()->AppendNewToTop(item); + } + } + nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, + aLists, item, DisplayRows); +} + +nsIFrame::LogicalSides +nsTableRowGroupFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return LogicalSides(); + } + + LogicalSides skip; + if (nullptr != GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (nullptr != GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +// Position and size aKidFrame and update our reflow state. +void +nsTableRowGroupFrame::PlaceChild(nsPresContext* aPresContext, + TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame, + WritingMode aWM, + const LogicalPoint& aKidPosition, + const nsSize& aContainerSize, + ReflowOutput& aDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidVisualOverflow) +{ + bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + // Place and size the child + FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr, + aWM, aKidPosition, aContainerSize, 0); + + nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, + aOriginalKidVisualOverflow, isFirstReflow); + + // Adjust the running block-offset + aReflowInput.bCoord += aDesiredSize.BSize(aWM); + + // If our block-size is constrained then update the available bsize + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(aWM)) { + aReflowInput.availSize.BSize(aWM) -= aDesiredSize.BSize(aWM); + } +} + +void +nsTableRowGroupFrame::InitChildReflowInput(nsPresContext& aPresContext, + bool aBorderCollapse, + ReflowInput& aReflowInput) +{ + nsMargin collapseBorder; + nsMargin padding(0,0,0,0); + nsMargin* pCollapseBorder = nullptr; + if (aBorderCollapse) { + nsTableRowFrame *rowFrame = do_QueryFrame(aReflowInput.mFrame); + if (rowFrame) { + WritingMode wm = GetWritingMode(); + LogicalMargin border = rowFrame->GetBCBorderWidth(wm); + collapseBorder = border.GetPhysicalMargin(wm); + pCollapseBorder = &collapseBorder; + } + } + aReflowInput.Init(&aPresContext, nullptr, pCollapseBorder, &padding); +} + +static void +CacheRowBSizesForPrinting(nsPresContext* aPresContext, + nsTableRowFrame* aFirstRow, + WritingMode aWM) +{ + for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) { + if (!row->GetPrevInFlow()) { + row->SetHasUnpaginatedBSize(true); + row->SetUnpaginatedBSize(aPresContext, row->BSize(aWM)); + } + } +} + +void +nsTableRowGroupFrame::ReflowChildren(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + TableRowGroupReflowInput& aReflowInput, + nsReflowStatus& aStatus, + bool* aPageBreakBeforeEnd) +{ + if (aPageBreakBeforeEnd) { + *aPageBreakBeforeEnd = false; + } + + WritingMode wm = aReflowInput.reflowInput.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.availSize.BSize(wm); + + bool haveRow = false; + bool reflowAllKids = aReflowInput.reflowInput.ShouldReflowAllKids() || + tableFrame->IsGeometryDirty(); + + // 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.reflowInput.ComputedSizeAsContainerIfConstrained(); + + nsIFrame *prevKidFrame = nullptr; + for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; + prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextSibling()) { + nsTableRowFrame *rowFrame = do_QueryFrame(kidFrame); + if (!rowFrame) { + // XXXldb nsCSSFrameConstructor needs to enforce this! + NS_NOTREACHED("yikes, a non-row child"); + continue; + } + nscoord cellSpacingB = tableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + haveRow = true; + + // Reflow the row frame + if (reflowAllKids || + NS_SUBTREE_DIRTY(kidFrame) || + (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow && + (isPaginated || + kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { + LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize); + nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect(); + + // XXXldb We used to only pass aDesiredSize.mFlags through for the + // incremental reflow codepath. + ReflowOutput desiredSize(aReflowInput.reflowInput, + aDesiredSize.mFlags); + desiredSize.ClearSize(); + + // 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.availSize; + kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput kidReflowInput(aPresContext, aReflowInput.reflowInput, + kidFrame, kidAvailSize, + nullptr, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, borderCollapse, kidReflowInput); + + // This can indicate that columns were resized. + if (aReflowInput.reflowInput.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.bCoord); + ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowInput, + wm, kidPosition, containerSize, 0, aStatus); + kidReflowInput.ApplyRelativePositioning(&kidPosition, containerSize); + + // Place the child + PlaceChild(aPresContext, aReflowInput, kidFrame, + wm, kidPosition, containerSize, + desiredSize, oldKidRect.GetPhysicalRect(wm, containerSize), + oldKidVisualOverflow); + aReflowInput.bCoord += cellSpacingB; + + if (!reflowAllKids) { + if (IsSimpleRowFrame(aReflowInput.tableFrame, rowFrame)) { + // Inform the row of its new bsize. + rowFrame->DidResize(); + // the overflow area may have changed inflate the overflow area + const nsStylePosition *stylePos = StylePosition(); + nsStyleUnit unit = stylePos->BSize(wm).GetUnit(); + if (aReflowInput.tableFrame->IsAutoBSize(wm) && + unit != eStyleUnit_Coord) { + // Because other cells in the row may need to be aligned + // differently, repaint the entire row + InvalidateFrame(); + } else if (oldKidRect.BSize(wm) != desiredSize.BSize(wm)) { + needToCalcRowBSizes = true; + } + } else { + needToCalcRowBSizes = true; + } + } + + if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) { + nsTableRowFrame* nextRow = rowFrame->GetNextRow(); + if (nextRow) { + *aPageBreakBeforeEnd = nsTableFrame::PageBreakAfter(kidFrame, nextRow); + } + } + } else { + SlideChild(aReflowInput, kidFrame); + + // Adjust the running b-offset so we know where the next row should be placed + nscoord bSize = kidFrame->BSize(wm) + cellSpacingB; + aReflowInput.bCoord += bSize; + + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) { + aReflowInput.availSize.BSize(wm) -= bSize; + } + } + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + } + + if (haveRow) { + aReflowInput.bCoord -= tableFrame->GetRowSpacing(GetStartRowIndex() + + GetRowCount()); + } + + // Return our desired rect + aDesiredSize.ISize(wm) = aReflowInput.reflowInput.AvailableISize(); + aDesiredSize.BSize(wm) = aReflowInput.bCoord; + + if (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow) { + DidResizeRows(aDesiredSize); + if (isPaginated) { + CacheRowBSizesForPrinting(aPresContext, GetFirstRow(), wm); + } + } + else if (needToCalcRowBSizes) { + CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.reflowInput); + if (!reflowAllKids) { + InvalidateFrame(); + } + } +} + +nsTableRowFrame* +nsTableRowGroupFrame::GetFirstRow() +{ + for (nsIFrame* childFrame : mFrames) { + nsTableRowFrame* rowFrame = do_QueryFrame(childFrame); + if (rowFrame) { + return rowFrame; + } + } + return nullptr; +} + +nsTableRowFrame* +nsTableRowGroupFrame::GetLastRow() +{ + for (auto iter = mFrames.rbegin(), end = mFrames.rend(); iter != end; ++iter) { + nsTableRowFrame* rowFrame = do_QueryFrame(*iter); + if (rowFrame) { + return rowFrame; + } + } + return nullptr; +} + + +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; + + nsTArray<RowInfo> rowInfo; + if (!rowInfo.AppendElements(numRows)) { + return; + } + + 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) { + nscoord cellSpacingB = 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 += cellSpacingB; + } + 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(); + rowFrame->CalculateCellActualBSize(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 rowVisualOverflow = rowFrame->GetVisualOverflowRect(); + 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, rowVisualOverflow, + 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(aPresContext, 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 = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); + if (collapseGroup) { + tableFrame->SetNeedToCollapse(true); + } + + nsOverflowAreas 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 oldGroupVisualOverflow = GetVisualOverflowRect(); + + 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, oldGroupVisualOverflow, + false); + + return bGroupOffset; +} + +// Move a child that was skipped during a reflow. +void +nsTableRowGroupFrame::SlideChild(TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame) +{ + // Move the frame if we need to. + WritingMode wm = aReflowInput.reflowInput.GetWritingMode(); + const nsSize containerSize = + aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained(); + LogicalPoint oldPosition = + aKidFrame->GetLogicalNormalPosition(wm, containerSize); + LogicalPoint newPosition = oldPosition; + newPosition.B(wm) = aReflowInput.bCoord; + if (oldPosition.B(wm) != newPosition.B(wm)) { + aKidFrame->InvalidateFrameSubtree(); + aReflowInput.reflowInput.ApplyRelativePositioning(&newPosition, + containerSize); + aKidFrame->SetPosition(wm, newPosition, containerSize); + nsTableFrame::RePositionViews(aKidFrame); + aKidFrame->InvalidateFrameSubtree(); + } +} + +// Create a continuing frame, add it to the child list, and then push it +// and the frames that follow +void +nsTableRowGroupFrame::CreateContinuingRowFrame(nsPresContext& aPresContext, + nsIFrame& aRowFrame, + nsIFrame** aContRowFrame) +{ + // XXX what is the row index? + if (!aContRowFrame) {NS_ASSERTION(false, "bad call"); return;} + // create the continuing frame which will create continuing cell frames + *aContRowFrame = aPresContext.PresShell()->FrameConstructor()-> + CreateContinuingFrame(&aPresContext, &aRowFrame, this); + + // Add the continuing row frame to the child list + mFrames.InsertFrame(nullptr, &aRowFrame, *aContRowFrame); + + // Push the continuing row frame and the frames that follow + PushChildren(*aContRowFrame, &aRowFrame); +} + +// 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, + nsTableRowFrame*& aContRow, + nsTableRowFrame*& aFirstTruncatedRow, + nscoord& aDesiredBSize) +{ + NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes"); + aFirstTruncatedRow = nullptr; + aDesiredBSize = 0; + + 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(); + nsPoint rowPos = row->GetNormalPosition(); + // 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 + nscoord cellAvailBSize = aSpanningRowBEnd - rowPos.y; + NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?"); + bool isTopOfPage = (row == &aFirstRow) && aFirstRowIsTopOfPage; + + nsRect rowRect = row->GetNormalRect(); + nsSize rowAvailSize(aReflowInput.AvailableWidth(), + std::max(aReflowInput.AvailableHeight() - rowRect.y, + 0)); + // don't let the available height exceed what + // CalculateRowBSizes set for it + rowAvailSize.height = std::min(rowAvailSize.height, rowRect.height); + ReflowInput rowReflowInput(&aPresContext, aReflowInput, row, + LogicalSize(row->GetWritingMode(), + rowAvailSize), + nullptr, + ReflowInput::CALLER_WILL_INIT); + 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, rowPos.y + cellBSize); + if (NS_FRAME_IS_COMPLETE(status)) { + 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) { + CreateContinuingRowFrame(aPresContext, aLastRow, (nsIFrame**)&aContRow); + } + 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*>( + aPresContext.PresShell()->FrameConstructor()-> + CreateContinuingFrame(&aPresContext, cell, &aLastRow)); + int32_t colIndex; + cell->GetColIndex(colIndex); + aContRow->InsertCellFrame(contCell, colIndex); + } + } + } + } + } + } + if (!haveRowSpan) { + aDesiredBSize = aLastRow.GetNormalRect().YMost(); + } +} + +// 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(); + NS_PRECONDITION(mFrames.ContainsFrame(rowBefore), + "rowBefore not in our frame list?"); + + AutoFrameListPtr overflows(aPresContext, StealOverflowFrames()); + if (!rowBefore || !overflows || overflows->IsEmpty() || + overflows->FirstChild() != aRow) { + NS_ERROR("invalid continued row"); + return; + } + + // 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(aRow); + + // Put the overflow rows into our child list + if (!overflows->IsEmpty()) { + mFrames.InsertFrames(nullptr, rowBefore, *overflows); + } +} + +static nsTableRowFrame* +GetRowBefore(nsTableRowFrame& aStartRow, + nsTableRowFrame& aRow) +{ + nsTableRowFrame* rowBefore = nullptr; + for (nsTableRowFrame* sib = &aStartRow; sib && (sib != &aRow); sib = sib->GetNextRow()) { + rowBefore = sib; + } + return rowBefore; +} + +nsresult +nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame, + nsReflowStatus& aStatus, + bool aRowForcedPageBreak) +{ + NS_PRECONDITION(aPresContext->IsPaginated(), "SplitRowGroup currently supports only paged media"); + + nsTableRowFrame* prevRowFrame = nullptr; + aDesiredSize.Height() = 0; + + nscoord availWidth = aReflowInput.AvailableWidth(); + nscoord availHeight = aReflowInput.AvailableHeight(); + + const bool borderCollapse = aTableFrame->IsBorderCollapse(); + + // get the page height + nscoord pageHeight = aPresContext->GetPageSize().height; + NS_ASSERTION(pageHeight != 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; + nscoord cellSpacingB = aTableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + nsRect rowRect = rowFrame->GetNormalRect(); + // See if the row fits on this page + if (rowRect.YMost() > availHeight) { + 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 || (availHeight - aDesiredSize.Height() > pageHeight / 20)) { + nsSize availSize(availWidth, std::max(availHeight - rowRect.y, 0)); + // don't let the available height exceed what CalculateRowHeights set for it + availSize.height = std::min(availSize.height, rowRect.height); + + ReflowInput rowReflowInput(aPresContext, aReflowInput, rowFrame, + LogicalSize(rowFrame->GetWritingMode(), + availSize), + nullptr, + ReflowInput::CALLER_WILL_INIT); + + 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 oldRowVisualOverflow = rowFrame->GetVisualOverflowRect(); + + // Reflow the cell with the constrained height. A cell with rowspan >1 will get this + // reflow later during SplitSpanningCells. + ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput, + 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); + rowFrame->SetSize(nsSize(rowMetrics.Width(), rowMetrics.Height())); + rowFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); + rowFrame->DidResize(); + + if (!aRowForcedPageBreak && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && + ShouldAvoidBreakInside(aReflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + + nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect, + oldRowVisualOverflow, + false); + + if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + // The row frame is incomplete and all of the rowspan 1 cells' block frames split + if ((rowMetrics.Height() <= rowReflowInput.AvailableHeight()) || 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 height exceeded the avail height, then there will be data loss + NS_ASSERTION(rowMetrics.Height() <= rowReflowInput.AvailableHeight(), + "data loss - incomplete row needed more height than available, on top of page"); + CreateContinuingRowFrame(*aPresContext, *rowFrame, (nsIFrame**)&contRow); + if (contRow) { + aDesiredSize.Height() += rowMetrics.Height(); + if (prevRowFrame) + aDesiredSize.Height() += cellSpacingB; + } + else return NS_ERROR_NULL_POINTER; + } + else { + // Put the row on the next page to give it more height + rowIsOnPage = false; + } + } + else { + // The row frame is complete because either (1) its minimum height is greater than the + // available height we gave it, or (2) it may have been given a larger height through + // style than its content, or (3) it contains a rowspan >1 cell which hasn't been + // reflowed with a constrained height yet (we will find out when SplitSpanningCells is + // called below) + if (rowMetrics.Height() > availSize.height || + (NS_INLINE_IS_BREAK_BEFORE(aStatus) && !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 = NS_FRAME_NOT_COMPLETE; + } + aDesiredSize.Height() += rowMetrics.Height(); + if (prevRowFrame) + aDesiredSize.Height() += cellSpacingB; + NS_WARNING("data loss - complete row needed more height 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 height + rowIsOnPage = false; + } + } + } + } //if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) + else { + // put the row on the next page to give it more height + rowIsOnPage = false; + } + + nsTableRowFrame* lastRowThisPage = rowFrame; + nscoord spanningRowBottom = availHeight; + if (!rowIsOnPage) { + NS_ASSERTION(!contRow, "We should not have created a continuation if none of this row fits"); + if (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + if (prevRowFrame) { + spanningRowBottom = prevRowFrame->GetNormalRect().YMost(); + lastRowThisPage = prevRowFrame; + isTopOfPage = (lastRowThisPage == firstRowThisPage) && aReflowInput.mFlags.mIsTopOfPage; + aStatus = NS_FRAME_NOT_COMPLETE; + } + else { + // We can't push children, so let our parent reflow us again with more space + aDesiredSize.Height() = rowRect.YMost(); + aStatus = NS_FRAME_COMPLETE; + break; + } + } + // reflow the cells with rowspan >1 that occur on the page + + nsTableRowFrame* firstTruncatedRow; + nscoord bMost; + SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, *firstRowThisPage, + *lastRowThisPage, aReflowInput.mFlags.mIsTopOfPage, spanningRowBottom, 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.Height() = rowRect.YMost(); + aStatus = NS_FRAME_COMPLETE; + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } + else { // (firstTruncatedRow != firstRowThisPage) + // Try to put firstTruncateRow on the next page + nsTableRowFrame* rowBefore = ::GetRowBefore(*firstRowThisPage, *firstTruncatedRow); + nscoord oldSpanningRowBottom = spanningRowBottom; + spanningRowBottom = rowBefore->GetNormalRect().YMost(); + + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + nsTableRowFrame* oldLastRowThisPage = lastRowThisPage; + lastRowThisPage = rowBefore; + aStatus = NS_FRAME_NOT_COMPLETE; + + // Call SplitSpanningCells again with rowBefore as the last row on the page + SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, + *firstRowThisPage, *rowBefore, aReflowInput.mFlags.mIsTopOfPage, + spanningRowBottom, contRow, firstTruncatedRow, aDesiredSize.Height()); + 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; + spanningRowBottom = oldSpanningRowBottom; + SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, *firstRowThisPage, + *lastRowThisPage, aReflowInput.mFlags.mIsTopOfPage, spanningRowBottom, contRow, + firstTruncatedRow, aDesiredSize.Height()); + NS_WARNING("data loss in a row spanned cell"); + } + else { + // Let our parent reflow us again with more space + aDesiredSize.Height() = rowRect.YMost(); + aStatus = NS_FRAME_COMPLETE; + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } + } // if (firstTruncatedRow == firstRowThisPage) + } // if (firstTruncatedRow) + else { + aDesiredSize.Height() = std::max(aDesiredSize.Height(), bMost); + if (contRow) { + aStatus = NS_FRAME_NOT_COMPLETE; + } + } + if (NS_FRAME_IS_NOT_COMPLETE(aStatus) && !contRow) { + nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow(); + if (nextRow) { + PushChildren(nextRow, lastRowThisPage); + } + } + break; + } // if (rowRect.YMost() > availHeight) + else { + aDesiredSize.Height() = rowRect.YMost(); + prevRowFrame = rowFrame; + // see if there is a page break after the row + nsTableRowFrame* nextRow = rowFrame->GetNextRow(); + if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) { + PushChildren(nextRow, rowFrame); + aStatus = NS_FRAME_NOT_COMPLETE; + break; + } + } + // after the 1st row that has a height, we can't be on top + // of the page anymore. + isTopOfPage = isTopOfPage && rowRect.YMost() == 0; + } + return NS_OK; +} + +/** 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); + + aStatus = NS_FRAME_COMPLETE; + + // 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, tableFrame); + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_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. + if (aReflowInput.mFlags.mTableIsSplittable && + NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() && + (NS_FRAME_NOT_COMPLETE == aStatus || splitDueToPageBreak || + aDesiredSize.Height() > aReflowInput.AvailableHeight())) { + // Nope, find a place to split the row group + bool specialReflow = (bool)aReflowInput.mFlags.mSpecialBSizeReflow; + ((ReflowInput::ReflowInputFlags&)aReflowInput.mFlags).mSpecialBSizeReflow = false; + + SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus, + splitDueToPageBreak); + + ((ReflowInput::ReflowInputFlags&)aReflowInput.mFlags).mSpecialBSizeReflow = specialReflow; + } + + // 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()) { + NS_FRAME_SET_INCOMPLETE(aStatus); + } + + 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. + WritingMode wm = aReflowInput.GetWritingMode(); + 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) && + nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { + InvalidateFrame(); + } + + FinishAndStoreOverflow(&aDesiredSize); + + // Any absolutely-positioned children will get reflowed in + // nsFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +bool +nsTableRowGroupFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + // Row cursor invariants depend on the visual 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::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { + TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(), + GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void +nsTableRowGroupFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "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 (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + nsTableRowFrame *rowFrame = do_QueryFrame(e.get()); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION(mozilla::StyleDisplay::TableRow == + e.get()->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + rows.AppendElement(rowFrame); + } + } + + int32_t rowIndex = GetRowCount(); + // Append the frames to the sibling chain + mFrames.AppendFrames(nullptr, aFrameList); + + if (rows.Length() > 0) { + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->AppendRows(this, rowIndex, rows); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void +nsTableRowGroupFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "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 (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + nsTableRowFrame *rowFrame = do_QueryFrame(e.get()); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION(mozilla::StyleDisplay::TableRow == + e.get()->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, aFrameList); + + int32_t numRows = rows.Length(); + if (numRows > 0) { + nsTableRowFrame* prevRow = (nsTableRowFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, nsGkAtoms::tableRowFrame); + int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex; + tableFrame->InsertRows(this, rows, rowIndex, true); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void +nsTableRowGroupFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "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); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } + mFrames.DestroyFrame(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; +} + +nsIAtom* +nsTableRowGroupFrame::GetType() const +{ + return nsGkAtoms::tableRowGroupFrame; +} + +/** find page break before the first row **/ +bool +nsTableRowGroupFrame::HasInternalBreakBefore() const +{ + nsIFrame* firstChild = mFrames.FirstChild(); + if (!firstChild) + return false; + return firstChild->StyleDisplay()->mBreakBefore; +} + +/** find page break after the last row **/ +bool +nsTableRowGroupFrame::HasInternalBreakAfter() const +{ + nsIFrame* lastChild = mFrames.LastChild(); + if (!lastChild) + return false; + return lastChild->StyleDisplay()->mBreakAfter; +} +/* ----- global methods ----- */ + +nsTableRowGroupFrame* +NS_NewTableRowGroupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableRowGroupFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableRowGroup"), aResult); +} +#endif + +LogicalMargin +nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) +{ + LogicalMargin border(aWM); + nsTableRowFrame* firstRowFrame = nullptr; + nsTableRowFrame* lastRowFrame = nullptr; + for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame; rowFrame = rowFrame->GetNextRow()) { + if (!firstRowFrame) { + firstRowFrame = rowFrame; + } + lastRowFrame = rowFrame; + } + if (firstRowFrame) { + border.BStart(aWM) = nsPresContext:: + CSSPixelsToAppUnits(firstRowFrame->GetBStartBCBorderWidth()); + border.BEnd(aWM) = nsPresContext:: + CSSPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth()); + } + return border; +} + +void nsTableRowGroupFrame::SetContinuousBCBorderWidth(LogicalSide aForSide, + BCPixelSize aPixelValue) +{ + switch (aForSide) { + case eLogicalSideIEnd: + mIEndContBorderWidth = aPixelValue; + return; + case eLogicalSideBEnd: + mBEndContBorderWidth = aPixelValue; + return; + case eLogicalSideIStart: + mIStartContBorderWidth = aPixelValue; + return; + default: + NS_ERROR("invalid LogicalSide argument"); + } +} + +//nsILineIterator methods +int32_t +nsTableRowGroupFrame::GetNumLines() +{ + return GetRowCount(); +} + +bool +nsTableRowGroupFrame::GetDirection() +{ + return (NS_STYLE_DIRECTION_RTL == + GetTableFrame()->StyleVisibility()->mDirection); +} + +NS_IMETHODIMP +nsTableRowGroupFrame::GetLine(int32_t aLineNumber, + nsIFrame** aFirstFrameOnLine, + int32_t* aNumFramesOnLine, + nsRect& aLineBounds) +{ + NS_ENSURE_ARG_POINTER(aFirstFrameOnLine); + NS_ENSURE_ARG_POINTER(aNumFramesOnLine); + + nsTableFrame* table = GetTableFrame(); + nsTableCellMap* cellMap = table->GetCellMap(); + + *aFirstFrameOnLine = nullptr; + *aNumFramesOnLine = 0; + aLineBounds.SetRect(0, 0, 0, 0); + + if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) { + return NS_OK; + } + aLineNumber += GetStartRowIndex(); + + *aNumFramesOnLine = cellMap->GetNumCellsOriginatingInRow(aLineNumber); + if (*aNumFramesOnLine == 0) { + return NS_OK; + } + int32_t colCount = table->GetColCount(); + for (int32_t i = 0; i < colCount; i++) { + CellData* data = cellMap->GetDataAt(aLineNumber, i); + if (data && data->IsOrig()) { + *aFirstFrameOnLine = (nsIFrame*)data->GetCellFrame(); + nsIFrame* parent = (*aFirstFrameOnLine)->GetParent(); + aLineBounds = parent->GetRect(); + return NS_OK; + } + } + NS_ERROR("cellmap is lying"); + return NS_ERROR_FAILURE; +} + +int32_t +nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame, int32_t aStartLine) +{ + NS_ENSURE_TRUE(aFrame, -1); + + nsTableRowFrame *rowFrame = do_QueryFrame(aFrame); + NS_ASSERTION(rowFrame, "RowGroup contains a frame that is not a row"); + + 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(); + + WritingMode wm = table->GetWritingMode(); + nsSize containerSize = table->GetSize(); + LogicalPoint pos(wm, aPos, containerSize); + + *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 = (NS_STYLE_DIRECTION_RTL == + table->StyleVisibility()->mDirection); + + nsIFrame* closestFromStart = nullptr; + nsIFrame* closestFromEnd = nullptr; + int32_t n = numCells; + nsIFrame* firstFrame = frame; + while (n--) { + LogicalRect rect = frame->GetLogicalRect(wm, containerSize); + if (rect.ISize(wm) > 0) { + // If pos.I() is inside this frame - this is it + if (rect.IStart(wm) <= pos.I(wm) && rect.IEnd(wm) > pos.I(wm)) { + closestFromStart = closestFromEnd = frame; + break; + } + if (rect.IStart(wm) < pos.I(wm)) { + if (!closestFromStart || + rect.IEnd(wm) > closestFromStart-> + GetLogicalRect(wm, containerSize).IEnd(wm)) + closestFromStart = frame; + } + else { + if (!closestFromEnd || + rect.IStart(wm) < closestFromEnd-> + GetLogicalRect(wm, containerSize).IStart(wm)) + closestFromEnd = frame; + } + } + frame = frame->GetNextSibling(); + } + if (!closestFromStart && !closestFromEnd) { + // All frames were zero-width. Just take the first one. + closestFromStart = closestFromEnd = firstFrame; + } + *aPosIsBeforeFirstFrame = isRTL ? !closestFromEnd : !closestFromStart; + *aPosIsAfterLastFrame = isRTL ? !closestFromStart : !closestFromEnd; + if (closestFromStart == closestFromEnd) { + *aFrameFound = closestFromStart; + } + else if (!closestFromStart) { + *aFrameFound = closestFromEnd; + } + else if (!closestFromEnd) { + *aFrameFound = closestFromStart; + } + else { // we're between two frames + nscoord delta = + closestFromEnd->GetLogicalRect(wm, containerSize).IStart(wm) - + closestFromStart->GetLogicalRect(wm, containerSize).IEnd(wm); + if (pos.I(wm) < closestFromStart-> + GetLogicalRect(wm, containerSize).IEnd(wm) + delta/2) { + *aFrameFound = closestFromStart; + } else { + *aFrameFound = closestFromEnd; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsTableRowGroupFrame::GetNextSiblingOnLine(nsIFrame*& aFrame, + int32_t aLineNumber) +{ + NS_ENSURE_ARG_POINTER(aFrame); + aFrame = aFrame->GetNextSibling(); + 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); + Properties().Delete(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(); + if (!data) + return nullptr; + Properties().Set(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 = Properties().Get(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->GetNormalRect().YMost() + property->mOverflowBelow > aY) { + --cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + while (cursorIndex + 1 < frameCount && + cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow <= aY) { + ++cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + + property->mCursorIndex = cursorIndex; + *aOverflowAbove = property->mOverflowAbove; + return cursorFrame; +} + +bool +nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) +{ + // Relative positioning can cause table parts to move, but we will still paint + // the backgrounds for the parts under them at their 'normal' position. That + // means that we must consider the overflow rects at both positions. For + // example, if we use relative positioning to move a row-spanning cell, we + // will still paint the row background for that cell at its normal position, + // which will overflow the row. + // XXX(seth): This probably isn't correct in the presence of transforms. + nsRect positionedOverflowRect = aFrame->GetVisualOverflowRect(); + 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); + return mFrames.AppendElement(aFrame) != nullptr; +} + +void +nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrame(aDisplayItemKey); + GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); +} + +void +nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); + // 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); +} |