/* -*- 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 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(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"); 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(); } return NS_OK; } // Handle the child-traversal part of DisplayGenericTablePart static void DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, const nsDisplayListSet& aLists) { nscoord overflowAbove; nsTableRowGroupFrame* f = static_cast(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) { // If we have a cursor, use it while (kid) { if (kid->GetRect().y - overflowAbove >= aBuilder->GetDirtyRect().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) { if (IsVisibleForPainting(aBuilder)) { // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted // just because we're visible? Or should it depend on the cell visibility // when we're not the whole table? // Paint the outset box-shadows for the table frames if (StyleEffects()->mBoxShadow) { aLists.BorderBackground()->AppendNewToTop( new (aBuilder) nsDisplayBoxShadowOuter (aBuilder, this)); } } for (nsTableRowFrame* row = GetFirstRow(); row; row = row->GetNextRow()) { if (!aBuilder->GetDirtyRect().Intersects(row->GetVisualOverflowRect() + row->GetNormalPosition())) { continue; } row->PaintCellBackgroundsForFrame(this, aBuilder, aLists, row->GetNormalPosition()); } if (IsVisibleForPainting(aBuilder)) { // XXXbz should box-shadow for rows/rowgroups/columns/colgroups get painted // just because we're visible? Or should it depend on the cell visibility // when we're not the whole table? // Paint the inset box-shadows for the table frames if (StyleEffects()->mBoxShadow) { aLists.BorderBackground()->AppendNewToTop( new (aBuilder) nsDisplayBoxShadowInner (aBuilder, this)); } } DisplayOutline(aBuilder, aLists); DisplayRows(aBuilder, this, aLists); } 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; 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( aPresContext.PresShell()->FrameConstructor()-> CreateContinuingFrame(&aPresContext, cell, &aLastRow)); uint32_t colIndex = cell->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 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 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); DeleteProperty(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; 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) { // Relative positioning can cause table parts to move, which can cause issues // with the cursor position to know which rows can be skipped. // To make this work, use the max difference between the frame's rect and the // union of the frame's "normal" position and the overflow rect to cover the // area of relatively positioned elements even if they are out of order. 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); }