michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "nsCOMPtr.h" michael@0: #include "nsTableRowGroupFrame.h" michael@0: #include "nsTableRowFrame.h" michael@0: #include "nsTableFrame.h" michael@0: #include "nsTableCellFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsIContent.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsDisplayList.h" michael@0: michael@0: #include "nsCellMap.h"//table cell navigation michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layout; michael@0: michael@0: nsTableRowGroupFrame::nsTableRowGroupFrame(nsStyleContext* aContext): michael@0: nsContainerFrame(aContext) michael@0: { michael@0: SetRepeatable(false); michael@0: } michael@0: michael@0: nsTableRowGroupFrame::~nsTableRowGroupFrame() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsTableRowGroupFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: if (GetStateBits() & NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN) { michael@0: nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot); michael@0: } michael@0: michael@0: nsContainerFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsTableRowGroupFrame) michael@0: NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: int32_t michael@0: nsTableRowGroupFrame::GetRowCount() michael@0: { michael@0: #ifdef DEBUG michael@0: for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) { michael@0: NS_ASSERTION(e.get()->StyleDisplay()->mDisplay == michael@0: NS_STYLE_DISPLAY_TABLE_ROW, michael@0: "Unexpected display"); michael@0: NS_ASSERTION(e.get()->GetType() == nsGkAtoms::tableRowFrame, michael@0: "Unexpected frame type"); michael@0: } michael@0: #endif michael@0: michael@0: return mFrames.GetLength(); michael@0: } michael@0: michael@0: int32_t nsTableRowGroupFrame::GetStartRowIndex() michael@0: { michael@0: int32_t result = -1; michael@0: if (mFrames.NotEmpty()) { michael@0: NS_ASSERTION(mFrames.FirstChild()->GetType() == nsGkAtoms::tableRowFrame, michael@0: "Unexpected frame type"); michael@0: result = static_cast(mFrames.FirstChild())->GetRowIndex(); michael@0: } michael@0: // if the row group doesn't have any children, get it the hard way michael@0: if (-1 == result) { michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: return tableFrame->GetStartRowIndex(this); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: void nsTableRowGroupFrame::AdjustRowIndices(int32_t aRowIndex, michael@0: int32_t anAdjustment) michael@0: { michael@0: nsIFrame* rowFrame = GetFirstPrincipalChild(); michael@0: for ( ; rowFrame; rowFrame = rowFrame->GetNextSibling()) { michael@0: if (NS_STYLE_DISPLAY_TABLE_ROW==rowFrame->StyleDisplay()->mDisplay) { michael@0: int32_t index = ((nsTableRowFrame*)rowFrame)->GetRowIndex(); michael@0: if (index >= aRowIndex) michael@0: ((nsTableRowFrame *)rowFrame)->SetRowIndex(index+anAdjustment); michael@0: } michael@0: } michael@0: } michael@0: nsresult michael@0: nsTableRowGroupFrame::InitRepeatedFrame(nsPresContext* aPresContext, michael@0: nsTableRowGroupFrame* aHeaderFooterFrame) michael@0: { michael@0: nsTableRowFrame* copyRowFrame = GetFirstRow(); michael@0: nsTableRowFrame* originalRowFrame = aHeaderFooterFrame->GetFirstRow(); michael@0: AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP); michael@0: while (copyRowFrame && originalRowFrame) { michael@0: copyRowFrame->AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP); michael@0: int rowIndex = originalRowFrame->GetRowIndex(); michael@0: copyRowFrame->SetRowIndex(rowIndex); michael@0: michael@0: // For each table cell frame set its column index michael@0: nsTableCellFrame* originalCellFrame = originalRowFrame->GetFirstCell(); michael@0: nsTableCellFrame* copyCellFrame = copyRowFrame->GetFirstCell(); michael@0: while (copyCellFrame && originalCellFrame) { michael@0: NS_ASSERTION(originalCellFrame->GetContent() == copyCellFrame->GetContent(), michael@0: "cell frames have different content"); michael@0: int32_t colIndex; michael@0: originalCellFrame->GetColIndex(colIndex); michael@0: copyCellFrame->SetColIndex(colIndex); michael@0: michael@0: // Move to the next cell frame michael@0: copyCellFrame = copyCellFrame->GetNextCell(); michael@0: originalCellFrame = originalCellFrame->GetNextCell(); michael@0: } michael@0: michael@0: // Move to the next row frame michael@0: originalRowFrame = originalRowFrame->GetNextRow(); michael@0: copyRowFrame = copyRowFrame->GetNextRow(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * We need a custom display item for table row backgrounds. This is only used michael@0: * when the table row is the root of a stacking context (e.g., has 'opacity'). michael@0: * Table row backgrounds can extend beyond the row frame bounds, when michael@0: * the row contains row-spanning cells. michael@0: */ michael@0: class nsDisplayTableRowGroupBackground : public nsDisplayTableItem { michael@0: public: michael@0: nsDisplayTableRowGroupBackground(nsDisplayListBuilder* aBuilder, michael@0: nsTableRowGroupFrame* aFrame) : michael@0: nsDisplayTableItem(aBuilder, aFrame) { michael@0: MOZ_COUNT_CTOR(nsDisplayTableRowGroupBackground); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayTableRowGroupBackground() { michael@0: MOZ_COUNT_DTOR(nsDisplayTableRowGroupBackground); michael@0: } michael@0: #endif michael@0: michael@0: virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) MOZ_OVERRIDE; michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: michael@0: NS_DISPLAY_DECL_NAME("TableRowGroupBackground", TYPE_TABLE_ROW_GROUP_BACKGROUND) michael@0: }; michael@0: michael@0: void michael@0: nsDisplayTableRowGroupBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) michael@0: { michael@0: if (aBuilder->ShouldSyncDecodeImages()) { michael@0: if (nsTableFrame::AnyTablePartHasUndecodedBackgroundImage(mFrame, mFrame->GetNextSibling())) { michael@0: bool snap; michael@0: aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); michael@0: } michael@0: } michael@0: michael@0: nsDisplayTableItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); michael@0: } michael@0: michael@0: void michael@0: nsDisplayTableRowGroupBackground::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(mFrame); michael@0: TableBackgroundPainter painter(tableFrame, michael@0: TableBackgroundPainter::eOrigin_TableRowGroup, michael@0: mFrame->PresContext(), *aCtx, michael@0: mVisibleRect, ToReferenceFrame(), michael@0: aBuilder->GetBackgroundPaintFlags()); michael@0: painter.PaintRowGroup(static_cast(mFrame)); michael@0: } michael@0: michael@0: // Handle the child-traversal part of DisplayGenericTablePart michael@0: static void michael@0: DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, michael@0: const nsRect& aDirtyRect, const nsDisplayListSet& aLists) michael@0: { michael@0: nscoord overflowAbove; michael@0: nsTableRowGroupFrame* f = static_cast(aFrame); michael@0: // Don't try to use the row cursor if we have to descend into placeholders; michael@0: // we might have rows containing placeholders, where the row's overflow michael@0: // area doesn't intersect the dirty rect but we need to descend into the row michael@0: // to see out of flows. michael@0: // Note that we really want to check ShouldDescendIntoFrame for all michael@0: // the rows in |f|, but that's exactly what we're trying to avoid, so we michael@0: // approximate it by checking it for |f|: if it's true for any row michael@0: // in |f| then it's true for |f| itself. michael@0: nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(f) ? michael@0: nullptr : f->GetFirstRowContaining(aDirtyRect.y, &overflowAbove); michael@0: michael@0: if (kid) { michael@0: // have a cursor, use it michael@0: while (kid) { michael@0: if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost()) michael@0: break; michael@0: f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // No cursor. Traverse children the hard way and build a cursor while we're at it michael@0: nsTableRowGroupFrame::FrameCursorData* cursor = f->SetupRowCursor(); michael@0: kid = f->GetFirstPrincipalChild(); michael@0: while (kid) { michael@0: f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); michael@0: michael@0: if (cursor) { michael@0: if (!cursor->AppendFrame(kid)) { michael@0: f->ClearRowCursor(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: if (cursor) { michael@0: cursor->FinishBuildingCursor(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: nsDisplayTableItem* item = nullptr; michael@0: if (IsVisibleInSelection(aBuilder)) { michael@0: bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext(); michael@0: if (isRoot) { michael@0: // This background is created regardless of whether this frame is michael@0: // visible or not. Visibility decisions are delegated to the michael@0: // table background painter. michael@0: item = new (aBuilder) nsDisplayTableRowGroupBackground(aBuilder, this); michael@0: aLists.BorderBackground()->AppendNewToTop(item); michael@0: } michael@0: } michael@0: nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, michael@0: aLists, item, DisplayRows); michael@0: } michael@0: michael@0: int michael@0: nsTableRowGroupFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const michael@0: { michael@0: int skip = 0; michael@0: if (nullptr != GetPrevInFlow()) { michael@0: skip |= LOGICAL_SIDE_B_START; michael@0: } michael@0: if (nullptr != GetNextInFlow()) { michael@0: skip |= LOGICAL_SIDE_B_END; michael@0: } michael@0: return skip; michael@0: } michael@0: michael@0: // Position and size aKidFrame and update our reflow state. The origin of michael@0: // aKidRect is relative to the upper-left origin of our frame michael@0: void michael@0: nsTableRowGroupFrame::PlaceChild(nsPresContext* aPresContext, michael@0: nsRowGroupReflowState& aReflowState, michael@0: nsIFrame* aKidFrame, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsRect& aOriginalKidRect, michael@0: const nsRect& aOriginalKidVisualOverflow) michael@0: { michael@0: bool isFirstReflow = michael@0: (aKidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0; michael@0: michael@0: // Place and size the child michael@0: FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr, 0, michael@0: aReflowState.y, 0); michael@0: michael@0: nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, michael@0: aOriginalKidVisualOverflow, isFirstReflow); michael@0: michael@0: // Adjust the running y-offset michael@0: aReflowState.y += aDesiredSize.Height(); michael@0: michael@0: // If our height is constrained then update the available height michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { michael@0: aReflowState.availSize.height -= aDesiredSize.Height(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableRowGroupFrame::InitChildReflowState(nsPresContext& aPresContext, michael@0: bool aBorderCollapse, michael@0: nsHTMLReflowState& aReflowState) michael@0: { michael@0: nsMargin collapseBorder; michael@0: nsMargin padding(0,0,0,0); michael@0: nsMargin* pCollapseBorder = nullptr; michael@0: if (aBorderCollapse) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(aReflowState.frame); michael@0: if (rowFrame) { michael@0: pCollapseBorder = rowFrame->GetBCBorderWidth(collapseBorder); michael@0: } michael@0: } michael@0: aReflowState.Init(&aPresContext, -1, -1, pCollapseBorder, &padding); michael@0: } michael@0: michael@0: static void michael@0: CacheRowHeightsForPrinting(nsPresContext* aPresContext, michael@0: nsTableRowFrame* aFirstRow) michael@0: { michael@0: for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) { michael@0: if (!row->GetPrevInFlow()) { michael@0: row->SetHasUnpaginatedHeight(true); michael@0: row->SetUnpaginatedHeight(aPresContext, row->GetSize().height); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowGroupFrame::ReflowChildren(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: nsRowGroupReflowState& aReflowState, michael@0: nsReflowStatus& aStatus, michael@0: bool* aPageBreakBeforeEnd) michael@0: { michael@0: if (aPageBreakBeforeEnd) michael@0: *aPageBreakBeforeEnd = false; michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nsresult rv = NS_OK; michael@0: const bool borderCollapse = tableFrame->IsBorderCollapse(); michael@0: nscoord cellSpacingY = tableFrame->GetCellSpacingY(); michael@0: michael@0: // XXXldb Should we really be checking this rather than available height? michael@0: // (Think about multi-column layout!) michael@0: bool isPaginated = aPresContext->IsPaginated() && michael@0: NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height; michael@0: michael@0: bool haveRow = false; michael@0: bool reflowAllKids = aReflowState.reflowState.ShouldReflowAllKids() || michael@0: tableFrame->IsGeometryDirty(); michael@0: bool needToCalcRowHeights = reflowAllKids; michael@0: michael@0: nsIFrame *prevKidFrame = nullptr; michael@0: for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; michael@0: prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextSibling()) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(kidFrame); michael@0: if (!rowFrame) { michael@0: // XXXldb nsCSSFrameConstructor needs to enforce this! michael@0: NS_NOTREACHED("yikes, a non-row child"); michael@0: continue; michael@0: } michael@0: michael@0: haveRow = true; michael@0: michael@0: // Reflow the row frame michael@0: if (reflowAllKids || michael@0: NS_SUBTREE_DIRTY(kidFrame) || michael@0: (aReflowState.reflowState.mFlags.mSpecialHeightReflow && michael@0: (isPaginated || (kidFrame->GetStateBits() & michael@0: NS_FRAME_CONTAINS_RELATIVE_HEIGHT)))) { michael@0: nsRect oldKidRect = kidFrame->GetRect(); michael@0: nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect(); michael@0: michael@0: // XXXldb We used to only pass aDesiredSize.mFlags through for the michael@0: // incremental reflow codepath. michael@0: nsHTMLReflowMetrics desiredSize(aReflowState.reflowState, michael@0: aDesiredSize.mFlags); michael@0: desiredSize.Width() = desiredSize.Height() = 0; michael@0: michael@0: // Reflow the child into the available space, giving it as much height as michael@0: // it wants. We'll deal with splitting later after we've computed the row michael@0: // heights, taking into account cells with row spans... michael@0: nsSize kidAvailSize(aReflowState.availSize.width, NS_UNCONSTRAINEDSIZE); michael@0: nsHTMLReflowState kidReflowState(aPresContext, aReflowState.reflowState, michael@0: kidFrame, kidAvailSize, michael@0: -1, -1, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(*aPresContext, borderCollapse, kidReflowState); michael@0: michael@0: // This can indicate that columns were resized. michael@0: if (aReflowState.reflowState.mFlags.mHResize) michael@0: kidReflowState.mFlags.mHResize = true; michael@0: michael@0: NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame, michael@0: "If we're not on the first frame, we should have a " michael@0: "previous sibling..."); michael@0: // If prev row has nonzero YMost, then we can't be at the top of the page michael@0: if (prevKidFrame && prevKidFrame->GetRect().YMost() > 0) { michael@0: kidReflowState.mFlags.mIsTopOfPage = false; michael@0: } michael@0: michael@0: rv = ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState, michael@0: 0, aReflowState.y, 0, aStatus); michael@0: michael@0: // Place the child michael@0: PlaceChild(aPresContext, aReflowState, kidFrame, desiredSize, michael@0: oldKidRect, oldKidVisualOverflow); michael@0: aReflowState.y += cellSpacingY; michael@0: michael@0: if (!reflowAllKids) { michael@0: if (IsSimpleRowFrame(aReflowState.tableFrame, kidFrame)) { michael@0: // Inform the row of its new height. michael@0: rowFrame->DidResize(); michael@0: // the overflow area may have changed inflate the overflow area michael@0: const nsStylePosition *stylePos = StylePosition(); michael@0: nsStyleUnit unit = stylePos->mHeight.GetUnit(); michael@0: if (aReflowState.tableFrame->IsAutoHeight() && michael@0: unit != eStyleUnit_Coord) { michael@0: // Because other cells in the row may need to be aligned michael@0: // differently, repaint the entire row michael@0: nsRect kidRect(0, aReflowState.y, michael@0: desiredSize.Width(), desiredSize.Height()); michael@0: InvalidateFrame(); michael@0: } michael@0: else if (oldKidRect.height != desiredSize.Height()) michael@0: needToCalcRowHeights = true; michael@0: } else { michael@0: needToCalcRowHeights = true; michael@0: } michael@0: } michael@0: michael@0: if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) { michael@0: nsTableRowFrame* nextRow = rowFrame->GetNextRow(); michael@0: if (nextRow) { michael@0: *aPageBreakBeforeEnd = nsTableFrame::PageBreakAfter(kidFrame, nextRow); michael@0: } michael@0: } michael@0: } else { michael@0: SlideChild(aReflowState, kidFrame); michael@0: michael@0: // Adjust the running y-offset so we know where the next row should be placed michael@0: nscoord height = kidFrame->GetSize().height + cellSpacingY; michael@0: aReflowState.y += height; michael@0: michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { michael@0: aReflowState.availSize.height -= height; michael@0: } michael@0: } michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); michael@0: } michael@0: michael@0: if (haveRow) michael@0: aReflowState.y -= cellSpacingY; michael@0: michael@0: // Return our desired rect michael@0: aDesiredSize.Width() = aReflowState.reflowState.AvailableWidth(); michael@0: aDesiredSize.Height() = aReflowState.y; michael@0: michael@0: if (aReflowState.reflowState.mFlags.mSpecialHeightReflow) { michael@0: DidResizeRows(aDesiredSize); michael@0: if (isPaginated) { michael@0: CacheRowHeightsForPrinting(aPresContext, GetFirstRow()); michael@0: } michael@0: } michael@0: else if (needToCalcRowHeights) { michael@0: CalculateRowHeights(aPresContext, aDesiredSize, aReflowState.reflowState); michael@0: if (!reflowAllKids) { michael@0: InvalidateFrame(); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsTableRowFrame* michael@0: nsTableRowGroupFrame::GetFirstRow() michael@0: { michael@0: for (nsIFrame* childFrame = mFrames.FirstChild(); childFrame; michael@0: childFrame = childFrame->GetNextSibling()) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(childFrame); michael@0: if (rowFrame) { michael@0: return rowFrame; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: struct RowInfo { michael@0: RowInfo() { height = pctHeight = hasStyleHeight = hasPctHeight = isSpecial = 0; } michael@0: unsigned height; // content height or fixed height, excluding pct height michael@0: unsigned pctHeight:29; // pct height michael@0: unsigned hasStyleHeight:1; michael@0: unsigned hasPctHeight:1; michael@0: unsigned isSpecial:1; // there is no cell originating in the row with rowspan=1 and there are at michael@0: // least 2 cells spanning the row and there is no style height on the row michael@0: }; michael@0: michael@0: static void michael@0: UpdateHeights(RowInfo& aRowInfo, michael@0: nscoord aAdditionalHeight, michael@0: nscoord& aTotal, michael@0: nscoord& aUnconstrainedTotal) michael@0: { michael@0: aRowInfo.height += aAdditionalHeight; michael@0: aTotal += aAdditionalHeight; michael@0: if (!aRowInfo.hasStyleHeight) { michael@0: aUnconstrainedTotal += aAdditionalHeight; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableRowGroupFrame::DidResizeRows(nsHTMLReflowMetrics& aDesiredSize) michael@0: { michael@0: // update the cells spanning rows with their new heights michael@0: // this is the place where all of the cells in the row get set to the height of the row michael@0: // Reset the overflow area michael@0: aDesiredSize.mOverflowAreas.Clear(); michael@0: for (nsTableRowFrame* rowFrame = GetFirstRow(); michael@0: rowFrame; rowFrame = rowFrame->GetNextRow()) { michael@0: rowFrame->DidResize(); michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame); michael@0: } michael@0: } michael@0: michael@0: // This calculates the height of all the rows and takes into account michael@0: // style height on the row group, style heights on rows and cells, style heights on rowspans. michael@0: // Actual row heights will be adjusted later if the table has a style height. michael@0: // Even if rows don't change height, this method must be called to set the heights of each michael@0: // cell in the row to the height of its row. michael@0: void michael@0: nsTableRowGroupFrame::CalculateRowHeights(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState) michael@0: { michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: const bool isPaginated = aPresContext->IsPaginated(); michael@0: michael@0: // all table cells have the same top and bottom margins, namely cellSpacingY michael@0: nscoord cellSpacingY = tableFrame->GetCellSpacingY(); michael@0: michael@0: int32_t numEffCols = tableFrame->GetEffectiveColCount(); michael@0: michael@0: int32_t startRowIndex = GetStartRowIndex(); michael@0: // find the row corresponding to the row index we just found michael@0: nsTableRowFrame* startRowFrame = GetFirstRow(); michael@0: michael@0: if (!startRowFrame) return; michael@0: michael@0: // the current row group height is the y origin of the 1st row we are about to calculated a height for michael@0: nscoord startRowGroupHeight = startRowFrame->GetPosition().y; michael@0: michael@0: int32_t numRows = GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex()); michael@0: // collect the current height of each row. nscoord* rowHeights = nullptr; michael@0: if (numRows <= 0) michael@0: return; michael@0: michael@0: nsTArray rowInfo; michael@0: if (!rowInfo.AppendElements(numRows)) { michael@0: return; michael@0: } michael@0: michael@0: bool hasRowSpanningCell = false; michael@0: nscoord heightOfRows = 0; michael@0: nscoord heightOfUnStyledRows = 0; michael@0: // Get the height of each row without considering rowspans. This will be the max of michael@0: // the largest desired height of each cell, the largest style height of each cell, michael@0: // the style height of the row. michael@0: nscoord pctHeightBasis = GetHeightBasis(aReflowState); michael@0: int32_t rowIndex; // the index in rowInfo, not among the rows in the row group michael@0: nsTableRowFrame* rowFrame; michael@0: for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) { michael@0: nscoord nonPctHeight = rowFrame->GetContentHeight(); michael@0: if (isPaginated) { michael@0: nonPctHeight = std::max(nonPctHeight, rowFrame->GetSize().height); michael@0: } michael@0: if (!rowFrame->GetPrevInFlow()) { michael@0: if (rowFrame->HasPctHeight()) { michael@0: rowInfo[rowIndex].hasPctHeight = true; michael@0: rowInfo[rowIndex].pctHeight = rowFrame->GetHeight(pctHeightBasis); michael@0: } michael@0: rowInfo[rowIndex].hasStyleHeight = rowFrame->HasStyleHeight(); michael@0: nonPctHeight = std::max(nonPctHeight, rowFrame->GetFixedHeight()); michael@0: } michael@0: UpdateHeights(rowInfo[rowIndex], nonPctHeight, heightOfRows, heightOfUnStyledRows); michael@0: michael@0: if (!rowInfo[rowIndex].hasStyleHeight) { michael@0: if (isPaginated || tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) { michael@0: rowInfo[rowIndex].isSpecial = true; michael@0: // iteratate the row's cell frames to see if any do not have rowspan > 1 michael@0: nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); michael@0: while (cellFrame) { michael@0: int32_t rowSpan = tableFrame->GetEffectiveRowSpan(rowIndex + startRowIndex, *cellFrame); michael@0: if (1 == rowSpan) { michael@0: rowInfo[rowIndex].isSpecial = false; michael@0: break; michael@0: } michael@0: cellFrame = cellFrame->GetNextCell(); michael@0: } michael@0: } michael@0: } michael@0: // See if a cell spans into the row. If so we'll have to do the next step michael@0: if (!hasRowSpanningCell) { michael@0: if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) { michael@0: hasRowSpanningCell = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (hasRowSpanningCell) { michael@0: // Get the height of cells with rowspans and allocate any extra space to the rows they span michael@0: // iteratate the child frames and process the row frames among them michael@0: for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) { michael@0: // See if the row has an originating cell with rowspan > 1. We cannot determine this for a row in a michael@0: // continued row group by calling RowHasSpanningCells, because the row's fif may not have any originating michael@0: // cells yet the row may have a continued cell which originates in it. michael@0: if (GetPrevInFlow() || tableFrame->RowHasSpanningCells(startRowIndex + rowIndex, numEffCols)) { michael@0: nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); michael@0: // iteratate the row's cell frames michael@0: while (cellFrame) { michael@0: int32_t rowSpan = tableFrame->GetEffectiveRowSpan(rowIndex + startRowIndex, *cellFrame); michael@0: if ((rowIndex + rowSpan) > numRows) { michael@0: // there might be rows pushed already to the nextInFlow michael@0: rowSpan = numRows - rowIndex; michael@0: } michael@0: if (rowSpan > 1) { // a cell with rowspan > 1, determine the height of the rows it spans michael@0: nscoord heightOfRowsSpanned = 0; michael@0: nscoord heightOfUnStyledRowsSpanned = 0; michael@0: nscoord numSpecialRowsSpanned = 0; michael@0: nscoord cellSpacingTotal = 0; michael@0: int32_t spanX; michael@0: for (spanX = 0; spanX < rowSpan; spanX++) { michael@0: heightOfRowsSpanned += rowInfo[rowIndex + spanX].height; michael@0: if (!rowInfo[rowIndex + spanX].hasStyleHeight) { michael@0: heightOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].height; michael@0: } michael@0: if (0 != spanX) { michael@0: cellSpacingTotal += cellSpacingY; michael@0: } michael@0: if (rowInfo[rowIndex + spanX].isSpecial) { michael@0: numSpecialRowsSpanned++; michael@0: } michael@0: } michael@0: nscoord heightOfAreaSpanned = heightOfRowsSpanned + cellSpacingTotal; michael@0: // get the height of the cell michael@0: nsSize cellFrameSize = cellFrame->GetSize(); michael@0: nsSize cellDesSize = cellFrame->GetDesiredSize(); michael@0: rowFrame->CalculateCellActualHeight(cellFrame, cellDesSize.height); michael@0: cellFrameSize.height = cellDesSize.height; michael@0: if (cellFrame->HasVerticalAlignBaseline()) { michael@0: // to ensure that a spanning cell with a long descender doesn't michael@0: // collide with the next row, we need to take into account the shift michael@0: // that will be done to align the cell on the baseline of the row. michael@0: cellFrameSize.height += rowFrame->GetMaxCellAscent() - michael@0: cellFrame->GetCellBaseline(); michael@0: } michael@0: michael@0: if (heightOfAreaSpanned < cellFrameSize.height) { michael@0: // the cell's height is larger than the available space of the rows it michael@0: // spans so distribute the excess height to the rows affected michael@0: nscoord extra = cellFrameSize.height - heightOfAreaSpanned; michael@0: nscoord extraUsed = 0; michael@0: if (0 == numSpecialRowsSpanned) { michael@0: //NS_ASSERTION(heightOfRowsSpanned > 0, "invalid row span situation"); michael@0: bool haveUnStyledRowsSpanned = (heightOfUnStyledRowsSpanned > 0); michael@0: nscoord divisor = (haveUnStyledRowsSpanned) michael@0: ? heightOfUnStyledRowsSpanned : heightOfRowsSpanned; michael@0: if (divisor > 0) { michael@0: for (spanX = rowSpan - 1; spanX >= 0; spanX--) { michael@0: if (!haveUnStyledRowsSpanned || !rowInfo[rowIndex + spanX].hasStyleHeight) { michael@0: // The amount of additional space each row gets is proportional to its height michael@0: float percent = ((float)rowInfo[rowIndex + spanX].height) / ((float)divisor); michael@0: michael@0: // give rows their percentage, except for the first row which gets the remainder michael@0: nscoord extraForRow = (0 == spanX) ? extra - extraUsed michael@0: : NSToCoordRound(((float)(extra)) * percent); michael@0: extraForRow = std::min(extraForRow, extra - extraUsed); michael@0: // update the row height michael@0: UpdateHeights(rowInfo[rowIndex + spanX], extraForRow, heightOfRows, heightOfUnStyledRows); michael@0: extraUsed += extraForRow; michael@0: if (extraUsed >= extra) { michael@0: NS_ASSERTION((extraUsed == extra), "invalid row height calculation"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // put everything in the last row michael@0: UpdateHeights(rowInfo[rowIndex + rowSpan - 1], extra, heightOfRows, heightOfUnStyledRows); michael@0: } michael@0: } michael@0: else { michael@0: // give the extra to the special rows michael@0: nscoord numSpecialRowsAllocated = 0; michael@0: for (spanX = rowSpan - 1; spanX >= 0; spanX--) { michael@0: if (rowInfo[rowIndex + spanX].isSpecial) { michael@0: // The amount of additional space each degenerate row gets is proportional to the number of them michael@0: float percent = 1.0f / ((float)numSpecialRowsSpanned); michael@0: michael@0: // give rows their percentage, except for the first row which gets the remainder michael@0: nscoord extraForRow = (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated) michael@0: ? extra - extraUsed michael@0: : NSToCoordRound(((float)(extra)) * percent); michael@0: extraForRow = std::min(extraForRow, extra - extraUsed); michael@0: // update the row height michael@0: UpdateHeights(rowInfo[rowIndex + spanX], extraForRow, heightOfRows, heightOfUnStyledRows); michael@0: extraUsed += extraForRow; michael@0: if (extraUsed >= extra) { michael@0: NS_ASSERTION((extraUsed == extra), "invalid row height calculation"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } // if (rowSpan > 1) michael@0: cellFrame = cellFrame->GetNextCell(); michael@0: } // while (cellFrame) michael@0: } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) { michael@0: } // while (rowFrame) michael@0: } michael@0: michael@0: // pct height rows have already got their content heights. Give them their pct heights up to pctHeightBasis michael@0: nscoord extra = pctHeightBasis - heightOfRows; michael@0: for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0); rowFrame = rowFrame->GetNextRow(), rowIndex++) { michael@0: RowInfo& rInfo = rowInfo[rowIndex]; michael@0: if (rInfo.hasPctHeight) { michael@0: nscoord rowExtra = (rInfo.pctHeight > rInfo.height) michael@0: ? rInfo.pctHeight - rInfo.height: 0; michael@0: rowExtra = std::min(rowExtra, extra); michael@0: UpdateHeights(rInfo, rowExtra, heightOfRows, heightOfUnStyledRows); michael@0: extra -= rowExtra; michael@0: } michael@0: } michael@0: michael@0: bool styleHeightAllocation = false; michael@0: nscoord rowGroupHeight = startRowGroupHeight + heightOfRows + ((numRows - 1) * cellSpacingY); michael@0: // if we have a style height, allocate the extra height to unconstrained rows michael@0: if ((aReflowState.ComputedHeight() > rowGroupHeight) && michael@0: (NS_UNCONSTRAINEDSIZE != aReflowState.ComputedHeight())) { michael@0: nscoord extraComputedHeight = aReflowState.ComputedHeight() - rowGroupHeight; michael@0: nscoord extraUsed = 0; michael@0: bool haveUnStyledRows = (heightOfUnStyledRows > 0); michael@0: nscoord divisor = (haveUnStyledRows) michael@0: ? heightOfUnStyledRows : heightOfRows; michael@0: if (divisor > 0) { michael@0: styleHeightAllocation = true; michael@0: for (rowIndex = 0; rowIndex < numRows; rowIndex++) { michael@0: if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleHeight) { michael@0: // The amount of additional space each row gets is based on the michael@0: // percentage of space it occupies michael@0: float percent = ((float)rowInfo[rowIndex].height) / ((float)divisor); michael@0: // give rows their percentage, except for the last row which gets the remainder michael@0: nscoord extraForRow = (numRows - 1 == rowIndex) michael@0: ? extraComputedHeight - extraUsed michael@0: : NSToCoordRound(((float)extraComputedHeight) * percent); michael@0: extraForRow = std::min(extraForRow, extraComputedHeight - extraUsed); michael@0: // update the row height michael@0: UpdateHeights(rowInfo[rowIndex], extraForRow, heightOfRows, heightOfUnStyledRows); michael@0: extraUsed += extraForRow; michael@0: if (extraUsed >= extraComputedHeight) { michael@0: NS_ASSERTION((extraUsed == extraComputedHeight), "invalid row height calculation"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: rowGroupHeight = aReflowState.ComputedHeight(); michael@0: } michael@0: michael@0: nscoord yOrigin = startRowGroupHeight; michael@0: // update the rows with their (potentially) new heights michael@0: for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) { michael@0: nsRect rowBounds = rowFrame->GetRect(); michael@0: nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); michael@0: michael@0: bool movedFrame = (rowBounds.y != yOrigin); michael@0: nscoord rowHeight = (rowInfo[rowIndex].height > 0) ? rowInfo[rowIndex].height : 0; michael@0: michael@0: if (movedFrame || (rowHeight != rowBounds.height)) { michael@0: // Resize/move the row to its final size and position michael@0: if (movedFrame) { michael@0: rowFrame->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: rowFrame->SetRect(nsRect(rowBounds.x, yOrigin, rowBounds.width, michael@0: rowHeight)); michael@0: michael@0: nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowVisualOverflow, michael@0: false); michael@0: } michael@0: if (movedFrame) { michael@0: nsTableFrame::RePositionViews(rowFrame); michael@0: // XXXbz we don't need to update our overflow area? michael@0: } michael@0: yOrigin += rowHeight + cellSpacingY; michael@0: } michael@0: michael@0: if (isPaginated && styleHeightAllocation) { michael@0: // since the row group has a style height, cache the row heights, so next in flows can honor them michael@0: CacheRowHeightsForPrinting(aPresContext, GetFirstRow()); michael@0: } michael@0: michael@0: DidResizeRows(aDesiredSize); michael@0: michael@0: aDesiredSize.Height() = rowGroupHeight; // Adjust our desired size michael@0: } michael@0: michael@0: nscoord michael@0: nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aYTotalOffset, michael@0: nscoord aWidth) michael@0: { michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: const nsStyleVisibility* groupVis = StyleVisibility(); michael@0: bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); michael@0: if (collapseGroup) { michael@0: tableFrame->SetNeedToCollapse(true); michael@0: } michael@0: michael@0: nsOverflowAreas overflow; michael@0: michael@0: nsTableRowFrame* rowFrame= GetFirstRow(); michael@0: bool didCollapse = false; michael@0: nscoord yGroupOffset = 0; michael@0: while (rowFrame) { michael@0: yGroupOffset += rowFrame->CollapseRowIfNecessary(yGroupOffset, michael@0: aWidth, collapseGroup, michael@0: didCollapse); michael@0: ConsiderChildOverflow(overflow, rowFrame); michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: michael@0: nsRect groupRect = GetRect(); michael@0: nsRect oldGroupRect = groupRect; michael@0: nsRect oldGroupVisualOverflow = GetVisualOverflowRect(); michael@0: michael@0: groupRect.height -= yGroupOffset; michael@0: if (didCollapse) { michael@0: // add back the cellspacing between rowgroups michael@0: groupRect.height += tableFrame->GetCellSpacingY(); michael@0: } michael@0: michael@0: groupRect.y -= aYTotalOffset; michael@0: groupRect.width = aWidth; michael@0: michael@0: if (aYTotalOffset != 0) { michael@0: InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: SetRect(groupRect); michael@0: overflow.UnionAllWith(nsRect(0, 0, groupRect.width, groupRect.height)); michael@0: FinishAndStoreOverflow(overflow, groupRect.Size()); michael@0: nsTableFrame::RePositionViews(this); michael@0: nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupVisualOverflow, michael@0: false); michael@0: michael@0: return yGroupOffset; michael@0: } michael@0: michael@0: // Move a child that was skipped during a reflow. michael@0: void michael@0: nsTableRowGroupFrame::SlideChild(nsRowGroupReflowState& aReflowState, michael@0: nsIFrame* aKidFrame) michael@0: { michael@0: // Move the frame if we need to michael@0: nsPoint oldPosition = aKidFrame->GetPosition(); michael@0: nsPoint newPosition = oldPosition; michael@0: newPosition.y = aReflowState.y; michael@0: if (oldPosition.y != newPosition.y) { michael@0: aKidFrame->InvalidateFrameSubtree(); michael@0: aKidFrame->SetPosition(newPosition); michael@0: nsTableFrame::RePositionViews(aKidFrame); michael@0: aKidFrame->InvalidateFrameSubtree(); michael@0: } michael@0: } michael@0: michael@0: // Create a continuing frame, add it to the child list, and then push it michael@0: // and the frames that follow michael@0: void michael@0: nsTableRowGroupFrame::CreateContinuingRowFrame(nsPresContext& aPresContext, michael@0: nsIFrame& aRowFrame, michael@0: nsIFrame** aContRowFrame) michael@0: { michael@0: // XXX what is the row index? michael@0: if (!aContRowFrame) {NS_ASSERTION(false, "bad call"); return;} michael@0: // create the continuing frame which will create continuing cell frames michael@0: *aContRowFrame = aPresContext.PresShell()->FrameConstructor()-> michael@0: CreateContinuingFrame(&aPresContext, &aRowFrame, this); michael@0: michael@0: // Add the continuing row frame to the child list michael@0: mFrames.InsertFrame(nullptr, &aRowFrame, *aContRowFrame); michael@0: michael@0: // Push the continuing row frame and the frames that follow michael@0: PushChildren(*aContRowFrame, &aRowFrame); michael@0: } michael@0: michael@0: // Reflow the cells with rowspan > 1 which originate between aFirstRow michael@0: // and end on or after aLastRow. aFirstTruncatedRow is the highest row on the michael@0: // page that contains a cell which cannot split on this page michael@0: void michael@0: nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsTableFrame& aTable, michael@0: nsTableRowFrame& aFirstRow, michael@0: nsTableRowFrame& aLastRow, michael@0: bool aFirstRowIsTopOfPage, michael@0: nscoord aSpanningRowBottom, michael@0: nsTableRowFrame*& aContRow, michael@0: nsTableRowFrame*& aFirstTruncatedRow, michael@0: nscoord& aDesiredHeight) michael@0: { michael@0: NS_ASSERTION(aSpanningRowBottom >= 0, "Can't split negative heights"); michael@0: aFirstTruncatedRow = nullptr; michael@0: aDesiredHeight = 0; michael@0: michael@0: const bool borderCollapse = aTable.IsBorderCollapse(); michael@0: int32_t lastRowIndex = aLastRow.GetRowIndex(); michael@0: bool wasLast = false; michael@0: bool haveRowSpan = false; michael@0: // Iterate the rows between aFirstRow and aLastRow michael@0: for (nsTableRowFrame* row = &aFirstRow; !wasLast; row = row->GetNextRow()) { michael@0: wasLast = (row == &aLastRow); michael@0: int32_t rowIndex = row->GetRowIndex(); michael@0: nsPoint rowPos = row->GetPosition(); michael@0: // Iterate the cells looking for those that have rowspan > 1 michael@0: for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) { michael@0: int32_t rowSpan = aTable.GetEffectiveRowSpan(rowIndex, *cell); michael@0: // Only reflow rowspan > 1 cells which span aLastRow. Those which don't span aLastRow michael@0: // were reflowed correctly during the unconstrained height reflow. michael@0: if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) { michael@0: haveRowSpan = true; michael@0: nsReflowStatus status; michael@0: // Ask the row to reflow the cell to the height of all the rows it spans up through aLastRow michael@0: // aAvailHeight is the space between the row group start and the end of the page michael@0: nscoord cellAvailHeight = aSpanningRowBottom - rowPos.y; michael@0: NS_ASSERTION(cellAvailHeight >= 0, "No space for cell?"); michael@0: bool isTopOfPage = (row == &aFirstRow) && aFirstRowIsTopOfPage; michael@0: michael@0: nsRect rowRect = row->GetRect(); michael@0: nsSize rowAvailSize(aReflowState.AvailableWidth(), michael@0: std::max(aReflowState.AvailableHeight() - rowRect.y, michael@0: 0)); michael@0: // don't let the available height exceed what michael@0: // CalculateRowHeights set for it michael@0: rowAvailSize.height = std::min(rowAvailSize.height, rowRect.height); michael@0: nsHTMLReflowState rowReflowState(&aPresContext, aReflowState, michael@0: row, rowAvailSize, michael@0: -1, -1, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(aPresContext, borderCollapse, rowReflowState); michael@0: rowReflowState.mFlags.mIsTopOfPage = isTopOfPage; // set top of page michael@0: michael@0: nscoord cellHeight = row->ReflowCellFrame(&aPresContext, rowReflowState, michael@0: isTopOfPage, cell, michael@0: cellAvailHeight, status); michael@0: aDesiredHeight = std::max(aDesiredHeight, rowPos.y + cellHeight); michael@0: if (NS_FRAME_IS_COMPLETE(status)) { michael@0: if (cellHeight > cellAvailHeight) { michael@0: aFirstTruncatedRow = row; michael@0: if ((row != &aFirstRow) || !aFirstRowIsTopOfPage) { michael@0: // return now, since we will be getting another reflow after either (1) row is michael@0: // moved to the next page or (2) the row group is moved to the next page michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: if (!aContRow) { michael@0: CreateContinuingRowFrame(aPresContext, aLastRow, (nsIFrame**)&aContRow); michael@0: } michael@0: if (aContRow) { michael@0: if (row != &aLastRow) { michael@0: // aContRow needs a continuation for cell, since cell spanned into aLastRow michael@0: // but does not originate there michael@0: nsTableCellFrame* contCell = static_cast( michael@0: aPresContext.PresShell()->FrameConstructor()-> michael@0: CreateContinuingFrame(&aPresContext, cell, &aLastRow)); michael@0: int32_t colIndex; michael@0: cell->GetColIndex(colIndex); michael@0: aContRow->InsertCellFrame(contCell, colIndex); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (!haveRowSpan) { michael@0: aDesiredHeight = aLastRow.GetRect().YMost(); michael@0: } michael@0: } michael@0: michael@0: // Remove the next-in-flow of the row, its cells and their cell blocks. This michael@0: // is necessary in case the row doesn't need a continuation later on or needs michael@0: // a continuation which doesn't have the same number of cells that now exist. michael@0: void michael@0: nsTableRowGroupFrame::UndoContinuedRow(nsPresContext* aPresContext, michael@0: nsTableRowFrame* aRow) michael@0: { michael@0: if (!aRow) return; // allow null aRow to avoid callers doing null checks michael@0: michael@0: // rowBefore was the prev-sibling of aRow's next-sibling before aRow was created michael@0: nsTableRowFrame* rowBefore = (nsTableRowFrame*)aRow->GetPrevInFlow(); michael@0: NS_PRECONDITION(mFrames.ContainsFrame(rowBefore), michael@0: "rowBefore not in our frame list?"); michael@0: michael@0: AutoFrameListPtr overflows(aPresContext, StealOverflowFrames()); michael@0: if (!rowBefore || !overflows || overflows->IsEmpty() || michael@0: overflows->FirstChild() != aRow) { michael@0: NS_ERROR("invalid continued row"); michael@0: return; michael@0: } michael@0: michael@0: // Destroy aRow, its cells, and their cell blocks. Cell blocks that have split michael@0: // will not have reflowed yet to pick up content from any overflow lines. michael@0: overflows->DestroyFrame(aRow); michael@0: michael@0: // Put the overflow rows into our child list michael@0: if (!overflows->IsEmpty()) { michael@0: mFrames.InsertFrames(nullptr, rowBefore, *overflows); michael@0: } michael@0: } michael@0: michael@0: static nsTableRowFrame* michael@0: GetRowBefore(nsTableRowFrame& aStartRow, michael@0: nsTableRowFrame& aRow) michael@0: { michael@0: nsTableRowFrame* rowBefore = nullptr; michael@0: for (nsTableRowFrame* sib = &aStartRow; sib && (sib != &aRow); sib = sib->GetNextRow()) { michael@0: rowBefore = sib; michael@0: } michael@0: return rowBefore; michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsTableFrame* aTableFrame, michael@0: nsReflowStatus& aStatus, michael@0: bool aRowForcedPageBreak) michael@0: { michael@0: NS_PRECONDITION(aPresContext->IsPaginated(), "SplitRowGroup currently supports only paged media"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsTableRowFrame* prevRowFrame = nullptr; michael@0: aDesiredSize.Height() = 0; michael@0: michael@0: nscoord availWidth = aReflowState.AvailableWidth(); michael@0: nscoord availHeight = aReflowState.AvailableHeight(); michael@0: michael@0: const bool borderCollapse = aTableFrame->IsBorderCollapse(); michael@0: nscoord cellSpacingY = aTableFrame->GetCellSpacingY(); michael@0: michael@0: // get the page height michael@0: nscoord pageHeight = aPresContext->GetPageSize().height; michael@0: NS_ASSERTION(pageHeight != NS_UNCONSTRAINEDSIZE, michael@0: "The table shouldn't be split when there should be space"); michael@0: michael@0: bool isTopOfPage = aReflowState.mFlags.mIsTopOfPage; michael@0: nsTableRowFrame* firstRowThisPage = GetFirstRow(); michael@0: michael@0: // Need to dirty the table's geometry, or else the row might skip michael@0: // reflowing its cell as an optimization. michael@0: aTableFrame->SetGeometryDirty(); michael@0: michael@0: // Walk each of the row frames looking for the first row frame that doesn't fit michael@0: // in the available space michael@0: for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame; rowFrame = rowFrame->GetNextRow()) { michael@0: bool rowIsOnPage = true; michael@0: nsRect rowRect = rowFrame->GetRect(); michael@0: // See if the row fits on this page michael@0: if (rowRect.YMost() > availHeight) { michael@0: nsTableRowFrame* contRow = nullptr; michael@0: // Reflow the row in the availabe space and have it split if it is the 1st michael@0: // row (on the page) or there is at least 5% of the current page available michael@0: // XXX this 5% should be made a preference michael@0: if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) { michael@0: nsSize availSize(availWidth, std::max(availHeight - rowRect.y, 0)); michael@0: // don't let the available height exceed what CalculateRowHeights set for it michael@0: availSize.height = std::min(availSize.height, rowRect.height); michael@0: michael@0: nsHTMLReflowState rowReflowState(aPresContext, aReflowState, michael@0: rowFrame, availSize, michael@0: -1, -1, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: michael@0: InitChildReflowState(*aPresContext, borderCollapse, rowReflowState); michael@0: rowReflowState.mFlags.mIsTopOfPage = isTopOfPage; // set top of page michael@0: nsHTMLReflowMetrics rowMetrics(aReflowState); michael@0: michael@0: // Get the old size before we reflow. michael@0: nsRect oldRowRect = rowFrame->GetRect(); michael@0: nsRect oldRowVisualOverflow = rowFrame->GetVisualOverflowRect(); michael@0: michael@0: // Reflow the cell with the constrained height. A cell with rowspan >1 will get this michael@0: // reflow later during SplitSpanningCells. michael@0: rv = ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowState, michael@0: 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rowFrame->SetSize(nsSize(rowMetrics.Width(), rowMetrics.Height())); michael@0: rowFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); michael@0: rowFrame->DidResize(); michael@0: michael@0: if (!aRowForcedPageBreak && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && michael@0: ShouldAvoidBreakInside(aReflowState)) { michael@0: aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: break; michael@0: } michael@0: michael@0: nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect, michael@0: oldRowVisualOverflow, michael@0: false); michael@0: michael@0: if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { michael@0: // The row frame is incomplete and all of the rowspan 1 cells' block frames split michael@0: if ((rowMetrics.Height() <= rowReflowState.AvailableHeight()) || isTopOfPage) { michael@0: // The row stays on this page because either it split ok or we're on the top of page. michael@0: // If top of page and the height exceeded the avail height, then there will be data loss michael@0: NS_ASSERTION(rowMetrics.Height() <= rowReflowState.AvailableHeight(), michael@0: "data loss - incomplete row needed more height than available, on top of page"); michael@0: CreateContinuingRowFrame(*aPresContext, *rowFrame, (nsIFrame**)&contRow); michael@0: if (contRow) { michael@0: aDesiredSize.Height() += rowMetrics.Height(); michael@0: if (prevRowFrame) michael@0: aDesiredSize.Height() += cellSpacingY; michael@0: } michael@0: else return NS_ERROR_NULL_POINTER; michael@0: } michael@0: else { michael@0: // Put the row on the next page to give it more height michael@0: rowIsOnPage = false; michael@0: } michael@0: } michael@0: else { michael@0: // The row frame is complete because either (1) its minimum height is greater than the michael@0: // available height we gave it, or (2) it may have been given a larger height through michael@0: // style than its content, or (3) it contains a rowspan >1 cell which hasn't been michael@0: // reflowed with a constrained height yet (we will find out when SplitSpanningCells is michael@0: // called below) michael@0: if (rowMetrics.Height() > availSize.height || michael@0: (NS_INLINE_IS_BREAK_BEFORE(aStatus) && !aRowForcedPageBreak)) { michael@0: // cases (1) and (2) michael@0: if (isTopOfPage) { michael@0: // We're on top of the page, so keep the row on this page. There will be data loss. michael@0: // Push the row frame that follows michael@0: nsTableRowFrame* nextRowFrame = rowFrame->GetNextRow(); michael@0: if (nextRowFrame) { michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: } michael@0: aDesiredSize.Height() += rowMetrics.Height(); michael@0: if (prevRowFrame) michael@0: aDesiredSize.Height() += cellSpacingY; michael@0: NS_WARNING("data loss - complete row needed more height than available, on top of page"); michael@0: } michael@0: else { michael@0: // We're not on top of the page, so put the row on the next page to give it more height michael@0: rowIsOnPage = false; michael@0: } michael@0: } michael@0: } michael@0: } //if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) michael@0: else { michael@0: // put the row on the next page to give it more height michael@0: rowIsOnPage = false; michael@0: } michael@0: michael@0: nsTableRowFrame* lastRowThisPage = rowFrame; michael@0: nscoord spanningRowBottom = availHeight; michael@0: if (!rowIsOnPage) { michael@0: NS_ASSERTION(!contRow, "We should not have created a continuation if none of this row fits"); michael@0: if (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowState)) { michael@0: aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: break; michael@0: } michael@0: if (prevRowFrame) { michael@0: spanningRowBottom = prevRowFrame->GetRect().YMost(); michael@0: lastRowThisPage = prevRowFrame; michael@0: isTopOfPage = (lastRowThisPage == firstRowThisPage) && aReflowState.mFlags.mIsTopOfPage; michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: } michael@0: else { michael@0: // We can't push children, so let our parent reflow us again with more space michael@0: aDesiredSize.Height() = rowRect.YMost(); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: break; michael@0: } michael@0: } michael@0: // reflow the cells with rowspan >1 that occur on the page michael@0: michael@0: nsTableRowFrame* firstTruncatedRow; michael@0: nscoord yMost; michael@0: SplitSpanningCells(*aPresContext, aReflowState, *aTableFrame, *firstRowThisPage, michael@0: *lastRowThisPage, aReflowState.mFlags.mIsTopOfPage, spanningRowBottom, contRow, michael@0: firstTruncatedRow, yMost); michael@0: if (firstTruncatedRow) { michael@0: // A rowspan >1 cell did not fit (and could not split) in the space we gave it michael@0: if (firstTruncatedRow == firstRowThisPage) { michael@0: if (aReflowState.mFlags.mIsTopOfPage) { michael@0: NS_WARNING("data loss in a row spanned cell"); michael@0: } michael@0: else { michael@0: // We can't push children, so let our parent reflow us again with more space michael@0: aDesiredSize.Height() = rowRect.YMost(); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: UndoContinuedRow(aPresContext, contRow); michael@0: contRow = nullptr; michael@0: } michael@0: } michael@0: else { // (firstTruncatedRow != firstRowThisPage) michael@0: // Try to put firstTruncateRow on the next page michael@0: nsTableRowFrame* rowBefore = ::GetRowBefore(*firstRowThisPage, *firstTruncatedRow); michael@0: nscoord oldSpanningRowBottom = spanningRowBottom; michael@0: spanningRowBottom = rowBefore->GetRect().YMost(); michael@0: michael@0: UndoContinuedRow(aPresContext, contRow); michael@0: contRow = nullptr; michael@0: nsTableRowFrame* oldLastRowThisPage = lastRowThisPage; michael@0: lastRowThisPage = rowBefore; michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: michael@0: // Call SplitSpanningCells again with rowBefore as the last row on the page michael@0: SplitSpanningCells(*aPresContext, aReflowState, *aTableFrame, michael@0: *firstRowThisPage, *rowBefore, aReflowState.mFlags.mIsTopOfPage, michael@0: spanningRowBottom, contRow, firstTruncatedRow, aDesiredSize.Height()); michael@0: if (firstTruncatedRow) { michael@0: if (aReflowState.mFlags.mIsTopOfPage) { michael@0: // We were better off with the 1st call to SplitSpanningCells, do it again michael@0: UndoContinuedRow(aPresContext, contRow); michael@0: contRow = nullptr; michael@0: lastRowThisPage = oldLastRowThisPage; michael@0: spanningRowBottom = oldSpanningRowBottom; michael@0: SplitSpanningCells(*aPresContext, aReflowState, *aTableFrame, *firstRowThisPage, michael@0: *lastRowThisPage, aReflowState.mFlags.mIsTopOfPage, spanningRowBottom, contRow, michael@0: firstTruncatedRow, aDesiredSize.Height()); michael@0: NS_WARNING("data loss in a row spanned cell"); michael@0: } michael@0: else { michael@0: // Let our parent reflow us again with more space michael@0: aDesiredSize.Height() = rowRect.YMost(); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: UndoContinuedRow(aPresContext, contRow); michael@0: contRow = nullptr; michael@0: } michael@0: } michael@0: } // if (firstTruncatedRow == firstRowThisPage) michael@0: } // if (firstTruncatedRow) michael@0: else { michael@0: aDesiredSize.Height() = std::max(aDesiredSize.Height(), yMost); michael@0: if (contRow) { michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: } michael@0: } michael@0: if (NS_FRAME_IS_NOT_COMPLETE(aStatus) && !contRow) { michael@0: nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow(); michael@0: if (nextRow) { michael@0: PushChildren(nextRow, lastRowThisPage); michael@0: } michael@0: } michael@0: break; michael@0: } // if (rowRect.YMost() > availHeight) michael@0: else { michael@0: aDesiredSize.Height() = rowRect.YMost(); michael@0: prevRowFrame = rowFrame; michael@0: // see if there is a page break after the row michael@0: nsTableRowFrame* nextRow = rowFrame->GetNextRow(); michael@0: if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) { michael@0: PushChildren(nextRow, rowFrame); michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: break; michael@0: } michael@0: } michael@0: // after the 1st row that has a height, we can't be on top michael@0: // of the page anymore. michael@0: isTopOfPage = isTopOfPage && rowRect.YMost() == 0; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** Layout the entire row group. michael@0: * This method stacks rows vertically according to HTML 4.0 rules. michael@0: * Rows are responsible for layout of their children. michael@0: */ michael@0: nsresult michael@0: nsTableRowGroupFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsTableRowGroupFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: michael@0: nsresult rv = NS_OK; michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: // Row geometry may be going to change so we need to invalidate any row cursor. michael@0: ClearRowCursor(); michael@0: michael@0: // see if a special height reflow needs to occur due to having a pct height michael@0: nsTableFrame::CheckRequestSpecialHeightReflow(aReflowState); michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nsRowGroupReflowState state(aReflowState, tableFrame); michael@0: const nsStyleVisibility* groupVis = StyleVisibility(); michael@0: bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); michael@0: if (collapseGroup) { michael@0: tableFrame->SetNeedToCollapse(true); michael@0: } michael@0: michael@0: // Check for an overflow list michael@0: MoveOverflowToChildList(); michael@0: michael@0: // Reflow the existing frames. michael@0: bool splitDueToPageBreak = false; michael@0: rv = ReflowChildren(aPresContext, aDesiredSize, state, aStatus, michael@0: &splitDueToPageBreak); michael@0: michael@0: // See if all the frames fit. Do not try to split anything if we're michael@0: // not paginated ... we can't split across columns yet. michael@0: if (aReflowState.mFlags.mTableIsSplittable && michael@0: NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight() && michael@0: (NS_FRAME_NOT_COMPLETE == aStatus || splitDueToPageBreak || michael@0: aDesiredSize.Height() > aReflowState.AvailableHeight())) { michael@0: // Nope, find a place to split the row group michael@0: bool specialReflow = (bool)aReflowState.mFlags.mSpecialHeightReflow; michael@0: ((nsHTMLReflowState::ReflowStateFlags&)aReflowState.mFlags).mSpecialHeightReflow = false; michael@0: michael@0: SplitRowGroup(aPresContext, aDesiredSize, aReflowState, tableFrame, aStatus, michael@0: splitDueToPageBreak); michael@0: michael@0: ((nsHTMLReflowState::ReflowStateFlags&)aReflowState.mFlags).mSpecialHeightReflow = specialReflow; michael@0: } michael@0: michael@0: // XXXmats The following is just bogus. We leave it here for now because michael@0: // ReflowChildren should pull up rows from our next-in-flow before returning michael@0: // a Complete status, but doesn't (bug 804888). michael@0: if (GetNextInFlow() && GetNextInFlow()->GetFirstPrincipalChild()) { michael@0: NS_FRAME_SET_INCOMPLETE(aStatus); michael@0: } michael@0: michael@0: SetHasStyleHeight((NS_UNCONSTRAINEDSIZE != aReflowState.ComputedHeight()) && michael@0: (aReflowState.ComputedHeight() > 0)); michael@0: michael@0: // just set our width to what was available. The table will calculate the width and not use our value. michael@0: aDesiredSize.Width() = aReflowState.AvailableWidth(); michael@0: michael@0: aDesiredSize.UnionOverflowAreasWithDesiredBounds(); michael@0: michael@0: // If our parent is in initial reflow, it'll handle invalidating our michael@0: // entire overflow rect. michael@0: if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) && michael@0: nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { michael@0: InvalidateFrame(); michael@0: } michael@0: michael@0: FinishAndStoreOverflow(&aDesiredSize); michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsTableRowGroupFrame::UpdateOverflow() michael@0: { michael@0: // Row cursor invariants depend on the visual overflow area of the rows, michael@0: // which may have changed, so we need to clear the cursor now. michael@0: ClearRowCursor(); michael@0: return nsContainerFrame::UpdateOverflow(); michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsTableRowGroupFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) michael@0: { michael@0: nsContainerFrame::DidSetStyleContext(aOldStyleContext); michael@0: michael@0: if (!aOldStyleContext) //avoid this on init michael@0: return; michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: if (tableFrame->IsBorderCollapse() && michael@0: tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { michael@0: nsIntRect damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(), michael@0: GetRowCount()); michael@0: tableFrame->AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowGroupFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: michael@0: ClearRowCursor(); michael@0: michael@0: // collect the new row frames in an array michael@0: // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here. michael@0: nsAutoTArray rows; michael@0: for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(e.get()); michael@0: NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); michael@0: if (rowFrame) { michael@0: NS_ASSERTION(NS_STYLE_DISPLAY_TABLE_ROW == michael@0: e.get()->StyleDisplay()->mDisplay, michael@0: "wrong display type on rowframe"); michael@0: rows.AppendElement(rowFrame); michael@0: } michael@0: } michael@0: michael@0: int32_t rowIndex = GetRowCount(); michael@0: // Append the frames to the sibling chain michael@0: mFrames.AppendFrames(nullptr, aFrameList); michael@0: michael@0: if (rows.Length() > 0) { michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: tableFrame->AppendRows(this, rowIndex, rows); michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: tableFrame->SetGeometryDirty(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowGroupFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, michael@0: "inserting after sibling frame with different parent"); michael@0: michael@0: ClearRowCursor(); michael@0: michael@0: // collect the new row frames in an array michael@0: // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here. michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nsTArray rows; michael@0: bool gotFirstRow = false; michael@0: for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(e.get()); michael@0: NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); michael@0: if (rowFrame) { michael@0: NS_ASSERTION(NS_STYLE_DISPLAY_TABLE_ROW == michael@0: e.get()->StyleDisplay()->mDisplay, michael@0: "wrong display type on rowframe"); michael@0: rows.AppendElement(rowFrame); michael@0: if (!gotFirstRow) { michael@0: rowFrame->SetFirstInserted(true); michael@0: gotFirstRow = true; michael@0: tableFrame->SetRowInserted(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t startRowIndex = GetStartRowIndex(); michael@0: // Insert the frames in the sibling chain michael@0: mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); michael@0: michael@0: int32_t numRows = rows.Length(); michael@0: if (numRows > 0) { michael@0: nsTableRowFrame* prevRow = (nsTableRowFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, nsGkAtoms::tableRowFrame); michael@0: int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex; michael@0: tableFrame->InsertRows(this, rows, rowIndex, true); michael@0: michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: tableFrame->SetGeometryDirty(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowGroupFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: michael@0: ClearRowCursor(); michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: // XXX why are we doing the QI stuff? There shouldn't be any non-rows here. michael@0: nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame); michael@0: if (rowFrame) { michael@0: // remove the rows from the table (and flag a rebalance) michael@0: tableFrame->RemoveRows(*rowFrame, 1, true); michael@0: michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: tableFrame->SetGeometryDirty(); michael@0: } michael@0: mFrames.DestroyFrame(aOldFrame); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableRowGroupFrame::GetUsedMargin() const michael@0: { michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableRowGroupFrame::GetUsedBorder() const michael@0: { michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableRowGroupFrame::GetUsedPadding() const michael@0: { michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: nscoord michael@0: nsTableRowGroupFrame::GetHeightBasis(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: nscoord result = 0; michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: if ((aReflowState.ComputedHeight() > 0) && (aReflowState.ComputedHeight() < NS_UNCONSTRAINEDSIZE)) { michael@0: nscoord cellSpacing = std::max(0, GetRowCount() - 1) * tableFrame->GetCellSpacingY(); michael@0: result = aReflowState.ComputedHeight() - cellSpacing; michael@0: } michael@0: else { michael@0: const nsHTMLReflowState* parentRS = aReflowState.parentReflowState; michael@0: if (parentRS && (tableFrame != parentRS->frame)) { michael@0: parentRS = parentRS->parentReflowState; michael@0: } michael@0: if (parentRS && (tableFrame == parentRS->frame) && michael@0: (parentRS->ComputedHeight() > 0) && (parentRS->ComputedHeight() < NS_UNCONSTRAINEDSIZE)) { michael@0: nscoord cellSpacing = std::max(0, tableFrame->GetRowCount() + 1) * tableFrame->GetCellSpacingY(); michael@0: result = parentRS->ComputedHeight() - cellSpacing; michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame, michael@0: nsIFrame* aFrame) michael@0: { michael@0: // Make sure it's a row frame and not a row group frame michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(aFrame); michael@0: if (rowFrame) { michael@0: int32_t rowIndex = rowFrame->GetRowIndex(); michael@0: michael@0: // It's a simple row frame if there are no cells that span into or michael@0: // across the row michael@0: int32_t numEffCols = aTableFrame->GetEffectiveColCount(); michael@0: if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) && michael@0: !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsTableRowGroupFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::tableRowGroupFrame; michael@0: } michael@0: michael@0: /** find page break before the first row **/ michael@0: bool michael@0: nsTableRowGroupFrame::HasInternalBreakBefore() const michael@0: { michael@0: nsIFrame* firstChild = mFrames.FirstChild(); michael@0: if (!firstChild) michael@0: return false; michael@0: return firstChild->StyleDisplay()->mBreakBefore; michael@0: } michael@0: michael@0: /** find page break after the last row **/ michael@0: bool michael@0: nsTableRowGroupFrame::HasInternalBreakAfter() const michael@0: { michael@0: nsIFrame* lastChild = mFrames.LastChild(); michael@0: if (!lastChild) michael@0: return false; michael@0: return lastChild->StyleDisplay()->mBreakAfter; michael@0: } michael@0: /* ----- global methods ----- */ michael@0: michael@0: nsIFrame* michael@0: NS_NewTableRowGroupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsTableRowGroupFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("TableRowGroup"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: nsMargin* michael@0: nsTableRowGroupFrame::GetBCBorderWidth(nsMargin& aBorder) michael@0: { michael@0: aBorder.left = aBorder.right = aBorder.top = aBorder.bottom = 0; michael@0: michael@0: nsTableRowFrame* firstRowFrame = nullptr; michael@0: nsTableRowFrame* lastRowFrame = nullptr; michael@0: for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame; rowFrame = rowFrame->GetNextRow()) { michael@0: if (!firstRowFrame) { michael@0: firstRowFrame = rowFrame; michael@0: } michael@0: lastRowFrame = rowFrame; michael@0: } michael@0: if (firstRowFrame) { michael@0: aBorder.top = nsPresContext::CSSPixelsToAppUnits(firstRowFrame->GetTopBCBorderWidth()); michael@0: aBorder.bottom = nsPresContext::CSSPixelsToAppUnits(lastRowFrame->GetBottomBCBorderWidth()); michael@0: } michael@0: michael@0: return &aBorder; michael@0: } michael@0: michael@0: void nsTableRowGroupFrame::SetContinuousBCBorderWidth(uint8_t aForSide, michael@0: BCPixelSize aPixelValue) michael@0: { michael@0: switch (aForSide) { michael@0: case NS_SIDE_RIGHT: michael@0: mRightContBorderWidth = aPixelValue; michael@0: return; michael@0: case NS_SIDE_BOTTOM: michael@0: mBottomContBorderWidth = aPixelValue; michael@0: return; michael@0: case NS_SIDE_LEFT: michael@0: mLeftContBorderWidth = aPixelValue; michael@0: return; michael@0: default: michael@0: NS_ERROR("invalid NS_SIDE argument"); michael@0: } michael@0: } michael@0: michael@0: //nsILineIterator methods michael@0: int32_t michael@0: nsTableRowGroupFrame::GetNumLines() michael@0: { michael@0: return GetRowCount(); michael@0: } michael@0: michael@0: bool michael@0: nsTableRowGroupFrame::GetDirection() michael@0: { michael@0: nsTableFrame* table = nsTableFrame::GetTableFrame(this); michael@0: return (NS_STYLE_DIRECTION_RTL == michael@0: table->StyleVisibility()->mDirection); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTableRowGroupFrame::GetLine(int32_t aLineNumber, michael@0: nsIFrame** aFirstFrameOnLine, michael@0: int32_t* aNumFramesOnLine, michael@0: nsRect& aLineBounds, michael@0: uint32_t* aLineFlags) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFirstFrameOnLine); michael@0: NS_ENSURE_ARG_POINTER(aNumFramesOnLine); michael@0: NS_ENSURE_ARG_POINTER(aLineFlags); michael@0: michael@0: nsTableFrame* table = nsTableFrame::GetTableFrame(this); michael@0: nsTableCellMap* cellMap = table->GetCellMap(); michael@0: michael@0: *aLineFlags = 0; michael@0: *aFirstFrameOnLine = nullptr; michael@0: *aNumFramesOnLine = 0; michael@0: aLineBounds.SetRect(0, 0, 0, 0); michael@0: michael@0: if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) { michael@0: return NS_OK; michael@0: } michael@0: aLineNumber += GetStartRowIndex(); michael@0: michael@0: *aNumFramesOnLine = cellMap->GetNumCellsOriginatingInRow(aLineNumber); michael@0: if (*aNumFramesOnLine == 0) { michael@0: return NS_OK; michael@0: } michael@0: int32_t colCount = table->GetColCount(); michael@0: for (int32_t i = 0; i < colCount; i++) { michael@0: CellData* data = cellMap->GetDataAt(aLineNumber, i); michael@0: if (data && data->IsOrig()) { michael@0: *aFirstFrameOnLine = (nsIFrame*)data->GetCellFrame(); michael@0: nsIFrame* parent = (*aFirstFrameOnLine)->GetParent(); michael@0: aLineBounds = parent->GetRect(); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: NS_ERROR("cellmap is lying"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: int32_t michael@0: nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame, int32_t aStartLine) michael@0: { michael@0: NS_ENSURE_TRUE(aFrame, -1); michael@0: michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(aFrame); michael@0: NS_ASSERTION(rowFrame, "RowGroup contains a frame that is not a row"); michael@0: michael@0: int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex(); michael@0: michael@0: return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTableRowGroupFrame::CheckLineOrder(int32_t aLine, michael@0: bool *aIsReordered, michael@0: nsIFrame **aFirstVisual, michael@0: nsIFrame **aLastVisual) michael@0: { michael@0: *aIsReordered = false; michael@0: *aFirstVisual = nullptr; michael@0: *aLastVisual = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber, michael@0: nscoord aX, michael@0: nsIFrame** aFrameFound, michael@0: bool* aXIsBeforeFirstFrame, michael@0: bool* aXIsAfterLastFrame) michael@0: { michael@0: nsTableFrame* table = nsTableFrame::GetTableFrame(this); michael@0: nsTableCellMap* cellMap = table->GetCellMap(); michael@0: michael@0: *aFrameFound = nullptr; michael@0: *aXIsBeforeFirstFrame = true; michael@0: *aXIsAfterLastFrame = false; michael@0: michael@0: aLineNumber += GetStartRowIndex(); michael@0: int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber); michael@0: if (numCells == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* frame = nullptr; michael@0: int32_t colCount = table->GetColCount(); michael@0: for (int32_t i = 0; i < colCount; i++) { michael@0: CellData* data = cellMap->GetDataAt(aLineNumber, i); michael@0: if (data && data->IsOrig()) { michael@0: frame = (nsIFrame*)data->GetCellFrame(); michael@0: break; michael@0: } michael@0: } michael@0: NS_ASSERTION(frame, "cellmap is lying"); michael@0: bool isRTL = (NS_STYLE_DIRECTION_RTL == michael@0: table->StyleVisibility()->mDirection); michael@0: michael@0: nsIFrame* closestFromLeft = nullptr; michael@0: nsIFrame* closestFromRight = nullptr; michael@0: int32_t n = numCells; michael@0: nsIFrame* firstFrame = frame; michael@0: while (n--) { michael@0: nsRect rect = frame->GetRect(); michael@0: if (rect.width > 0) { michael@0: // If aX is inside this frame - this is it michael@0: if (rect.x <= aX && rect.XMost() > aX) { michael@0: closestFromLeft = closestFromRight = frame; michael@0: break; michael@0: } michael@0: if (rect.x < aX) { michael@0: if (!closestFromLeft || michael@0: rect.XMost() > closestFromLeft->GetRect().XMost()) michael@0: closestFromLeft = frame; michael@0: } michael@0: else { michael@0: if (!closestFromRight || michael@0: rect.x < closestFromRight->GetRect().x) michael@0: closestFromRight = frame; michael@0: } michael@0: } michael@0: frame = frame->GetNextSibling(); michael@0: } michael@0: if (!closestFromLeft && !closestFromRight) { michael@0: // All frames were zero-width. Just take the first one. michael@0: closestFromLeft = closestFromRight = firstFrame; michael@0: } michael@0: *aXIsBeforeFirstFrame = isRTL ? !closestFromRight : !closestFromLeft; michael@0: *aXIsAfterLastFrame = isRTL ? !closestFromLeft : !closestFromRight; michael@0: if (closestFromLeft == closestFromRight) { michael@0: *aFrameFound = closestFromLeft; michael@0: } michael@0: else if (!closestFromLeft) { michael@0: *aFrameFound = closestFromRight; michael@0: } michael@0: else if (!closestFromRight) { michael@0: *aFrameFound = closestFromLeft; michael@0: } michael@0: else { // we're between two frames michael@0: nscoord delta = closestFromRight->GetRect().x - michael@0: closestFromLeft->GetRect().XMost(); michael@0: if (aX < closestFromLeft->GetRect().XMost() + delta/2) michael@0: *aFrameFound = closestFromLeft; michael@0: else michael@0: *aFrameFound = closestFromRight; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTableRowGroupFrame::GetNextSiblingOnLine(nsIFrame*& aFrame, michael@0: int32_t aLineNumber) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFrame); michael@0: aFrame = aFrame->GetNextSibling(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //end nsLineIterator methods michael@0: michael@0: static void michael@0: DestroyFrameCursorData(void* aPropertyValue) michael@0: { michael@0: delete static_cast(aPropertyValue); michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(RowCursorProperty, DestroyFrameCursorData) michael@0: michael@0: void michael@0: nsTableRowGroupFrame::ClearRowCursor() michael@0: { michael@0: if (!(GetStateBits() & NS_ROWGROUP_HAS_ROW_CURSOR)) michael@0: return; michael@0: michael@0: RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); michael@0: Properties().Delete(RowCursorProperty()); michael@0: } michael@0: michael@0: nsTableRowGroupFrame::FrameCursorData* michael@0: nsTableRowGroupFrame::SetupRowCursor() michael@0: { michael@0: if (GetStateBits() & NS_ROWGROUP_HAS_ROW_CURSOR) { michael@0: // We already have a valid row cursor. Don't waste time rebuilding it. michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIFrame* f = mFrames.FirstChild(); michael@0: int32_t count; michael@0: for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) { michael@0: f = f->GetNextSibling(); michael@0: } michael@0: if (!f) { michael@0: // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother michael@0: return nullptr; michael@0: } michael@0: michael@0: FrameCursorData* data = new FrameCursorData(); michael@0: if (!data) michael@0: return nullptr; michael@0: Properties().Set(RowCursorProperty(), data); michael@0: AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); michael@0: return data; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove) michael@0: { michael@0: if (!(GetStateBits() & NS_ROWGROUP_HAS_ROW_CURSOR)) michael@0: return nullptr; michael@0: michael@0: FrameCursorData* property = static_cast michael@0: (Properties().Get(RowCursorProperty())); michael@0: uint32_t cursorIndex = property->mCursorIndex; michael@0: uint32_t frameCount = property->mFrames.Length(); michael@0: if (cursorIndex >= frameCount) michael@0: return nullptr; michael@0: nsIFrame* cursorFrame = property->mFrames[cursorIndex]; michael@0: michael@0: // The cursor's frame list excludes frames with empty overflow-area, so michael@0: // we don't need to check that here. michael@0: michael@0: // We use property->mOverflowBelow here instead of computing the frame's michael@0: // true overflowArea.YMost(), because it is essential for the thresholds michael@0: // to form a monotonically increasing sequence. Otherwise we would break michael@0: // encountering a row whose overflowArea.YMost() is <= aY but which has michael@0: // a row above it containing cell(s) that span to include aY. michael@0: while (cursorIndex > 0 && michael@0: cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) { michael@0: --cursorIndex; michael@0: cursorFrame = property->mFrames[cursorIndex]; michael@0: } michael@0: while (cursorIndex + 1 < frameCount && michael@0: cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) { michael@0: ++cursorIndex; michael@0: cursorFrame = property->mFrames[cursorIndex]; michael@0: } michael@0: michael@0: property->mCursorIndex = cursorIndex; michael@0: *aOverflowAbove = property->mOverflowAbove; michael@0: return cursorFrame; michael@0: } michael@0: michael@0: bool michael@0: nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) michael@0: { michael@0: nsRect overflowRect = aFrame->GetVisualOverflowRect(); michael@0: if (overflowRect.IsEmpty()) michael@0: return true; michael@0: nscoord overflowAbove = -overflowRect.y; michael@0: nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height; michael@0: mOverflowAbove = std::max(mOverflowAbove, overflowAbove); michael@0: mOverflowBelow = std::max(mOverflowBelow, overflowBelow); michael@0: return mFrames.AppendElement(aFrame) != nullptr; michael@0: } michael@0: michael@0: void michael@0: nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey) michael@0: { michael@0: nsIFrame::InvalidateFrame(aDisplayItemKey); michael@0: GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); michael@0: } michael@0: michael@0: void michael@0: nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) michael@0: { michael@0: nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); michael@0: // If we have filters applied that would affects our bounds, then michael@0: // we get an inactive layer created and this is computed michael@0: // within FrameLayerBuilder michael@0: GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey); michael@0: }