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 "nsTableRowFrame.h" michael@0: #include "nsTableRowGroupFrame.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIContent.h" michael@0: #include "nsTableFrame.h" michael@0: #include "nsTableCellFrame.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsTableColGroupFrame.h" michael@0: #include "nsTableColFrame.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsIFrameInlines.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: struct nsTableCellReflowState : public nsHTMLReflowState michael@0: { michael@0: nsTableCellReflowState(nsPresContext* aPresContext, michael@0: const nsHTMLReflowState& aParentReflowState, michael@0: nsIFrame* aFrame, michael@0: const nsSize& aAvailableSpace, michael@0: uint32_t aFlags = 0) michael@0: : nsHTMLReflowState(aPresContext, aParentReflowState, aFrame, michael@0: aAvailableSpace, -1, -1, aFlags) michael@0: { michael@0: } michael@0: michael@0: void FixUp(const nsSize& aAvailSpace); michael@0: }; michael@0: michael@0: void nsTableCellReflowState::FixUp(const nsSize& aAvailSpace) michael@0: { michael@0: // fix the mComputed values during a pass 2 reflow since the cell can be a percentage base michael@0: NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aAvailSpace.width, michael@0: "have unconstrained width; this should only result from " michael@0: "very large sizes, not attempts at intrinsic width " michael@0: "calculation"); michael@0: if (NS_UNCONSTRAINEDSIZE != ComputedWidth()) { michael@0: nscoord computedWidth = michael@0: aAvailSpace.width - mComputedBorderPadding.LeftRight(); michael@0: computedWidth = std::max(0, computedWidth); michael@0: SetComputedWidth(computedWidth); michael@0: } michael@0: if (NS_UNCONSTRAINEDSIZE != ComputedHeight() && michael@0: NS_UNCONSTRAINEDSIZE != aAvailSpace.height) { michael@0: nscoord computedHeight = michael@0: aAvailSpace.height - mComputedBorderPadding.TopBottom(); michael@0: computedHeight = std::max(0, computedHeight); michael@0: SetComputedHeight(computedHeight); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::InitChildReflowState(nsPresContext& aPresContext, michael@0: const nsSize& aAvailSize, michael@0: bool aBorderCollapse, michael@0: nsTableCellReflowState& aReflowState) michael@0: { michael@0: nsMargin collapseBorder; michael@0: nsMargin* pCollapseBorder = nullptr; michael@0: if (aBorderCollapse) { michael@0: // we only reflow cells, so don't need to check frame type michael@0: nsBCTableCellFrame* bcCellFrame = (nsBCTableCellFrame*)aReflowState.frame; michael@0: if (bcCellFrame) { michael@0: pCollapseBorder = bcCellFrame->GetBorderWidth(collapseBorder); michael@0: } michael@0: } michael@0: aReflowState.Init(&aPresContext, -1, -1, pCollapseBorder); michael@0: aReflowState.FixUp(aAvailSize); michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::SetFixedHeight(nscoord aValue) michael@0: { michael@0: nscoord height = std::max(0, aValue); michael@0: if (HasFixedHeight()) { michael@0: if (height > mStyleFixedHeight) { michael@0: mStyleFixedHeight = height; michael@0: } michael@0: } michael@0: else { michael@0: mStyleFixedHeight = height; michael@0: if (height > 0) { michael@0: SetHasFixedHeight(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::SetPctHeight(float aPctValue, michael@0: bool aForce) michael@0: { michael@0: nscoord height = std::max(0, NSToCoordRound(aPctValue * 100.0f)); michael@0: if (HasPctHeight()) { michael@0: if ((height > mStylePctHeight) || aForce) { michael@0: mStylePctHeight = height; michael@0: } michael@0: } michael@0: else { michael@0: mStylePctHeight = height; michael@0: if (height > 0) { michael@0: SetHasPctHeight(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* ----------- nsTableRowFrame ---------- */ michael@0: michael@0: NS_QUERYFRAME_HEAD(nsTableRowFrame) michael@0: NS_QUERYFRAME_ENTRY(nsTableRowFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: nsTableRowFrame::nsTableRowFrame(nsStyleContext* aContext) michael@0: : nsContainerFrame(aContext) michael@0: { michael@0: mBits.mRowIndex = mBits.mFirstInserted = 0; michael@0: ResetHeight(0); michael@0: } michael@0: michael@0: nsTableRowFrame::~nsTableRowFrame() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: // Let the base class do its initialization michael@0: nsContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: NS_ASSERTION(NS_STYLE_DISPLAY_TABLE_ROW == StyleDisplay()->mDisplay, michael@0: "wrong display on table row frame"); michael@0: michael@0: if (aPrevInFlow) { michael@0: // Set the row index michael@0: nsTableRowFrame* rowFrame = (nsTableRowFrame*)aPrevInFlow; michael@0: michael@0: SetRowIndex(rowFrame->GetRowIndex()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::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: /* virtual */ void michael@0: nsTableRowFrame::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, GetRowIndex(), tableFrame->GetColCount(), 1); michael@0: tableFrame->AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: michael@0: const nsFrameList::Slice& newCells = mFrames.AppendFrames(nullptr, aFrameList); michael@0: michael@0: // Add the new cell frames to the table michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: for (nsFrameList::Enumerator e(newCells) ; !e.AtEnd(); e.Next()) { michael@0: nsIFrame *childFrame = e.get(); michael@0: NS_ASSERTION(IS_TABLE_CELL(childFrame->GetType()),"Not a table cell frame/pseudo frame construction failure"); michael@0: tableFrame->AppendCell(static_cast(*childFrame), GetRowIndex()); michael@0: } michael@0: michael@0: PresContext()->PresShell()->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: michael@0: nsresult michael@0: nsTableRowFrame::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: //Insert Frames in the frame list michael@0: const nsFrameList::Slice& newCells = mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); michael@0: michael@0: // Get the table frame michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nsIAtom* cellFrameType = tableFrame->IsBorderCollapse() ? nsGkAtoms::bcTableCellFrame : nsGkAtoms::tableCellFrame; michael@0: nsTableCellFrame* prevCellFrame = (nsTableCellFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, cellFrameType); michael@0: nsTArray cellChildren; michael@0: for (nsFrameList::Enumerator e(newCells); !e.AtEnd(); e.Next()) { michael@0: nsIFrame *childFrame = e.get(); michael@0: NS_ASSERTION(IS_TABLE_CELL(childFrame->GetType()),"Not a table cell frame/pseudo frame construction failure"); michael@0: cellChildren.AppendElement(static_cast(childFrame)); michael@0: } michael@0: // insert the cells into the cell map michael@0: int32_t colIndex = -1; michael@0: if (prevCellFrame) { michael@0: prevCellFrame->GetColIndex(colIndex); michael@0: } michael@0: tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex); michael@0: michael@0: PresContext()->PresShell()->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: nsTableRowFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(aOldFrame); michael@0: if (cellFrame) { michael@0: int32_t colIndex; michael@0: cellFrame->GetColIndex(colIndex); michael@0: // remove the cell from the cell map michael@0: tableFrame->RemoveCell(cellFrame, GetRowIndex()); michael@0: michael@0: // Remove the frame and destroy it michael@0: mFrames.DestroyFrame(aOldFrame); 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: else { michael@0: NS_ERROR("unexpected frame type"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableRowFrame::GetUsedMargin() const michael@0: { michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableRowFrame::GetUsedBorder() const michael@0: { michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableRowFrame::GetUsedPadding() const michael@0: { michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: nscoord michael@0: GetHeightOfRowsSpannedBelowFirst(nsTableCellFrame& aTableCellFrame, michael@0: nsTableFrame& aTableFrame) michael@0: { michael@0: nscoord height = 0; michael@0: nscoord cellSpacingY = aTableFrame.GetCellSpacingY(); michael@0: int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(aTableCellFrame); michael@0: // add in height of rows spanned beyond the 1st one michael@0: nsIFrame* nextRow = aTableCellFrame.GetParent()->GetNextSibling(); michael@0: for (int32_t rowX = 1; ((rowX < rowSpan) && nextRow);) { michael@0: if (nsGkAtoms::tableRowFrame == nextRow->GetType()) { michael@0: height += nextRow->GetSize().height; michael@0: rowX++; michael@0: } michael@0: height += cellSpacingY; michael@0: nextRow = nextRow->GetNextSibling(); michael@0: } michael@0: return height; michael@0: } michael@0: michael@0: nsTableCellFrame* michael@0: nsTableRowFrame::GetFirstCell() michael@0: { michael@0: nsIFrame* childFrame = mFrames.FirstChild(); michael@0: while (childFrame) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); michael@0: if (cellFrame) { michael@0: return cellFrame; michael@0: } michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: /** michael@0: * Post-reflow hook. This is where the table row does its post-processing michael@0: */ michael@0: void michael@0: nsTableRowFrame::DidResize() michael@0: { michael@0: // Resize and re-align the cell frames based on our row height michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nsTableIterator iter(*this); michael@0: nsIFrame* childFrame = iter.First(); michael@0: michael@0: nsHTMLReflowMetrics desiredSize(GetWritingMode()); // ??? michael@0: desiredSize.Width() = mRect.width; michael@0: desiredSize.Height() = mRect.height; michael@0: desiredSize.SetOverflowAreasToDesiredBounds(); michael@0: michael@0: while (childFrame) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); michael@0: if (cellFrame) { michael@0: nscoord cellHeight = mRect.height + GetHeightOfRowsSpannedBelowFirst(*cellFrame, *tableFrame); michael@0: michael@0: // resize the cell's height michael@0: nsRect cellRect = cellFrame->GetRect(); michael@0: nsRect cellVisualOverflow = cellFrame->GetVisualOverflowRect(); michael@0: if (cellRect.height != cellHeight) michael@0: { michael@0: cellFrame->SetSize(nsSize(cellRect.width, cellHeight)); michael@0: nsTableFrame::InvalidateTableFrame(cellFrame, cellRect, michael@0: cellVisualOverflow, michael@0: false); michael@0: } michael@0: michael@0: // realign cell content based on the new height. We might be able to michael@0: // skip this if the height didn't change... maybe. Hard to tell. michael@0: cellFrame->VerticallyAlignChild(mMaxCellAscent); michael@0: michael@0: // Always store the overflow, even if the height didn't change, since michael@0: // we'll lose part of our overflow area otherwise. michael@0: ConsiderChildOverflow(desiredSize.mOverflowAreas, cellFrame); michael@0: michael@0: // Note that if the cell's *content* needs to change in response michael@0: // to this height, it will get a special height reflow. michael@0: } michael@0: // Get the next child michael@0: childFrame = iter.Next(); michael@0: } michael@0: FinishAndStoreOverflow(&desiredSize); michael@0: if (HasView()) { michael@0: nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, GetView(), michael@0: desiredSize.VisualOverflow(), 0); michael@0: } michael@0: // Let our base class do the usual work michael@0: } michael@0: michael@0: // returns max-ascent amongst all cells that have 'vertical-align: baseline' michael@0: // *including* cells with rowspans michael@0: nscoord nsTableRowFrame::GetMaxCellAscent() const michael@0: { michael@0: return mMaxCellAscent; michael@0: } michael@0: michael@0: nscoord nsTableRowFrame::GetRowBaseline() michael@0: { michael@0: if(mMaxCellAscent) michael@0: return mMaxCellAscent; michael@0: michael@0: // If we don't have a baseline on any of the cells we go for the lowest michael@0: // content edge of the inner block frames. michael@0: // Every table cell has a cell frame with its border and padding. Inside michael@0: // the cell is a block frame. The cell is as high as the tallest cell in michael@0: // the parent row. As a consequence the block frame might not touch both michael@0: // the top and the bottom padding of it parent cell frame at the same time. michael@0: // michael@0: // bbbbbbbbbbbbbbbbbb cell border: b michael@0: // bppppppppppppppppb cell padding: p michael@0: // bpxxxxxxxxxxxxxxpb inner block: x michael@0: // bpx xpb michael@0: // bpx xpb michael@0: // bpx xpb michael@0: // bpxxxxxxxxxxxxxxpb base line michael@0: // bp pb michael@0: // bp pb michael@0: // bppppppppppppppppb michael@0: // bbbbbbbbbbbbbbbbbb michael@0: michael@0: nsTableIterator iter(*this); michael@0: nsIFrame* childFrame = iter.First(); michael@0: nscoord ascent = 0; michael@0: while (childFrame) { michael@0: if (IS_TABLE_CELL(childFrame->GetType())) { michael@0: nsIFrame* firstKid = childFrame->GetFirstPrincipalChild(); michael@0: ascent = std::max(ascent, firstKid->GetRect().YMost()); michael@0: } michael@0: // Get the next child michael@0: childFrame = iter.Next(); michael@0: } michael@0: return ascent; michael@0: } michael@0: nscoord michael@0: nsTableRowFrame::GetHeight(nscoord aPctBasis) const michael@0: { michael@0: nscoord height = 0; michael@0: if ((aPctBasis > 0) && HasPctHeight()) { michael@0: height = NSToCoordRound(GetPctHeight() * (float)aPctBasis); michael@0: } michael@0: if (HasFixedHeight()) { michael@0: height = std::max(height, GetFixedHeight()); michael@0: } michael@0: return std::max(height, GetContentHeight()); michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::ResetHeight(nscoord aFixedHeight) michael@0: { michael@0: SetHasFixedHeight(false); michael@0: SetHasPctHeight(false); michael@0: SetFixedHeight(0); michael@0: SetPctHeight(0); michael@0: SetContentHeight(0); michael@0: michael@0: if (aFixedHeight > 0) { michael@0: SetFixedHeight(aFixedHeight); michael@0: } michael@0: michael@0: mMaxCellAscent = 0; michael@0: mMaxCellDescent = 0; michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::UpdateHeight(nscoord aHeight, michael@0: nscoord aAscent, michael@0: nscoord aDescent, michael@0: nsTableFrame* aTableFrame, michael@0: nsTableCellFrame* aCellFrame) michael@0: { michael@0: if (!aTableFrame || !aCellFrame) { michael@0: NS_ASSERTION(false , "invalid call"); michael@0: return; michael@0: } michael@0: michael@0: if (aHeight != NS_UNCONSTRAINEDSIZE) { michael@0: if (!(aCellFrame->HasVerticalAlignBaseline())) { // only the cell's height matters michael@0: if (GetHeight() < aHeight) { michael@0: int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame); michael@0: if (rowSpan == 1) { michael@0: SetContentHeight(aHeight); michael@0: } michael@0: } michael@0: } michael@0: else { // the alignment on the baseline can change the height michael@0: NS_ASSERTION((aAscent != NS_UNCONSTRAINEDSIZE) && (aDescent != NS_UNCONSTRAINEDSIZE), "invalid call"); michael@0: // see if this is a long ascender michael@0: if (mMaxCellAscent < aAscent) { michael@0: mMaxCellAscent = aAscent; michael@0: } michael@0: // see if this is a long descender and without rowspan michael@0: if (mMaxCellDescent < aDescent) { michael@0: int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame); michael@0: if (rowSpan == 1) { michael@0: mMaxCellDescent = aDescent; michael@0: } michael@0: } michael@0: // keep the tallest height in sync michael@0: if (GetHeight() < mMaxCellAscent + mMaxCellDescent) { michael@0: SetContentHeight(mMaxCellAscent + mMaxCellDescent); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nscoord michael@0: nsTableRowFrame::CalcHeight(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: nscoord computedHeight = (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) michael@0: ? 0 : aReflowState.ComputedHeight(); michael@0: ResetHeight(computedHeight); michael@0: michael@0: const nsStylePosition* position = StylePosition(); michael@0: if (position->mHeight.ConvertsToLength()) { michael@0: SetFixedHeight(nsRuleNode::ComputeCoordPercentCalc(position->mHeight, 0)); michael@0: } michael@0: else if (eStyleUnit_Percent == position->mHeight.GetUnit()) { michael@0: SetPctHeight(position->mHeight.GetPercentValue()); michael@0: } michael@0: // calc() with percentages is treated like 'auto' on table rows. michael@0: michael@0: for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; michael@0: kidFrame = kidFrame->GetNextSibling()) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); michael@0: if (cellFrame) { michael@0: nsSize desSize = cellFrame->GetDesiredSize(); michael@0: if ((NS_UNCONSTRAINEDSIZE == aReflowState.AvailableHeight()) && !GetPrevInFlow()) { michael@0: CalculateCellActualHeight(cellFrame, desSize.height); michael@0: } michael@0: // height may have changed, adjust descent to absorb any excess difference michael@0: nscoord ascent; michael@0: if (!kidFrame->GetFirstPrincipalChild()->GetFirstPrincipalChild()) michael@0: ascent = desSize.height; michael@0: else michael@0: ascent = cellFrame->GetCellBaseline(); michael@0: nscoord descent = desSize.height - ascent; michael@0: UpdateHeight(desSize.height, ascent, descent, tableFrame, cellFrame); michael@0: } michael@0: } michael@0: return GetHeight(); 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 nsDisplayTableRowBackground : public nsDisplayTableItem { michael@0: public: michael@0: nsDisplayTableRowBackground(nsDisplayListBuilder* aBuilder, michael@0: nsTableRowFrame* aFrame) : michael@0: nsDisplayTableItem(aBuilder, aFrame) { michael@0: MOZ_COUNT_CTOR(nsDisplayTableRowBackground); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayTableRowBackground() { michael@0: MOZ_COUNT_DTOR(nsDisplayTableRowBackground); 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: NS_DISPLAY_DECL_NAME("TableRowBackground", TYPE_TABLE_ROW_BACKGROUND) michael@0: }; michael@0: michael@0: void michael@0: nsDisplayTableRowBackground::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: nsDisplayTableRowBackground::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_TableRow, michael@0: mFrame->PresContext(), *aCtx, michael@0: mVisibleRect, ToReferenceFrame(), michael@0: aBuilder->GetBackgroundPaintFlags()); michael@0: painter.PaintRow(static_cast(mFrame)); michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::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: // We would use nsDisplayGeneric for this rare case except that we michael@0: // need the background to be larger than the row frame in some michael@0: // cases. michael@0: item = new (aBuilder) nsDisplayTableRowBackground(aBuilder, this); michael@0: aLists.BorderBackground()->AppendNewToTop(item); michael@0: } michael@0: } michael@0: nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); michael@0: } michael@0: michael@0: int michael@0: nsTableRowFrame::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: // Calculate the cell's actual height given its pass2 height. michael@0: // Takes into account the specified height (in the style). michael@0: // Modifies the desired height that is passed in. michael@0: nsresult michael@0: nsTableRowFrame::CalculateCellActualHeight(nsTableCellFrame* aCellFrame, michael@0: nscoord& aDesiredHeight) michael@0: { michael@0: nscoord specifiedHeight = 0; michael@0: michael@0: // Get the height specified in the style information michael@0: const nsStylePosition* position = aCellFrame->StylePosition(); michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: int32_t rowSpan = tableFrame->GetEffectiveRowSpan(*aCellFrame); michael@0: michael@0: switch (position->mHeight.GetUnit()) { michael@0: case eStyleUnit_Calc: { michael@0: if (position->mHeight.CalcHasPercent()) { michael@0: // Treat this like "auto" michael@0: break; michael@0: } michael@0: // Fall through to the coord case michael@0: } michael@0: case eStyleUnit_Coord: { michael@0: nscoord outsideBoxSizing = 0; michael@0: // In quirks mode, table cell width should be content-box, but height michael@0: // should be border-box. michael@0: // Because of this historic anomaly, we do not use quirk.css michael@0: // (since we can't specify one value of box-sizing for width and another michael@0: // for height) michael@0: if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks) { michael@0: switch (position->mBoxSizing) { michael@0: case NS_STYLE_BOX_SIZING_CONTENT: michael@0: outsideBoxSizing = aCellFrame->GetUsedBorderAndPadding().TopBottom(); michael@0: break; michael@0: case NS_STYLE_BOX_SIZING_PADDING: michael@0: outsideBoxSizing = aCellFrame->GetUsedBorder().TopBottom(); michael@0: break; michael@0: default: michael@0: // NS_STYLE_BOX_SIZING_BORDER michael@0: break; michael@0: } michael@0: } michael@0: michael@0: specifiedHeight = michael@0: nsRuleNode::ComputeCoordPercentCalc(position->mHeight, 0) + michael@0: outsideBoxSizing; michael@0: michael@0: if (1 == rowSpan) michael@0: SetFixedHeight(specifiedHeight); michael@0: break; michael@0: } michael@0: case eStyleUnit_Percent: { michael@0: if (1 == rowSpan) michael@0: SetPctHeight(position->mHeight.GetPercentValue()); michael@0: // pct heights are handled when all of the cells are finished, so don't set specifiedHeight michael@0: break; michael@0: } michael@0: case eStyleUnit_Auto: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // If the specified height is greater than the desired height, then use the specified height michael@0: if (specifiedHeight > aDesiredHeight) michael@0: aDesiredHeight = specifiedHeight; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Calculates the available width for the table cell based on the known michael@0: // column widths taking into account column spans and column spacing michael@0: static nscoord michael@0: CalcAvailWidth(nsTableFrame& aTableFrame, michael@0: nsTableCellFrame& aCellFrame, michael@0: nscoord aCellSpacingX) michael@0: { michael@0: nscoord cellAvailWidth = 0; michael@0: int32_t colIndex; michael@0: aCellFrame.GetColIndex(colIndex); michael@0: int32_t colspan = aTableFrame.GetEffectiveColSpan(aCellFrame); michael@0: NS_ASSERTION(colspan > 0, "effective colspan should be positive"); michael@0: michael@0: for (int32_t spanX = 0; spanX < colspan; spanX++) { michael@0: cellAvailWidth += aTableFrame.GetColumnWidth(colIndex + spanX); michael@0: if (spanX > 0 && michael@0: aTableFrame.ColumnHasCellSpacingBefore(colIndex + spanX)) { michael@0: cellAvailWidth += aCellSpacingX; michael@0: } michael@0: } michael@0: return cellAvailWidth; michael@0: } michael@0: michael@0: nscoord michael@0: GetSpaceBetween(int32_t aPrevColIndex, michael@0: int32_t aColIndex, michael@0: int32_t aColSpan, michael@0: nsTableFrame& aTableFrame, michael@0: nscoord aCellSpacingX, michael@0: bool aIsLeftToRight, michael@0: bool aCheckVisibility) michael@0: { michael@0: nscoord space = 0; michael@0: int32_t colX; michael@0: if (aIsLeftToRight) { michael@0: for (colX = aPrevColIndex + 1; aColIndex > colX; colX++) { michael@0: bool isCollapsed = false; michael@0: if (!aCheckVisibility) { michael@0: space += aTableFrame.GetColumnWidth(colX); michael@0: } michael@0: else { michael@0: nsTableColFrame* colFrame = aTableFrame.GetColFrame(colX); michael@0: const nsStyleVisibility* colVis = colFrame->StyleVisibility(); michael@0: bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); michael@0: nsIFrame* cgFrame = colFrame->GetParent(); michael@0: const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); michael@0: bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == michael@0: groupVis->mVisible); michael@0: isCollapsed = collapseCol || collapseGroup; michael@0: if (!isCollapsed) michael@0: space += aTableFrame.GetColumnWidth(colX); michael@0: } michael@0: if (!isCollapsed && aTableFrame.ColumnHasCellSpacingBefore(colX)) { michael@0: space += aCellSpacingX; michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: int32_t lastCol = aColIndex + aColSpan - 1; michael@0: for (colX = aPrevColIndex - 1; colX > lastCol; colX--) { michael@0: bool isCollapsed = false; michael@0: if (!aCheckVisibility) { michael@0: space += aTableFrame.GetColumnWidth(colX); michael@0: } michael@0: else { michael@0: nsTableColFrame* colFrame = aTableFrame.GetColFrame(colX); michael@0: const nsStyleVisibility* colVis = colFrame->StyleVisibility(); michael@0: bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); michael@0: nsIFrame* cgFrame = colFrame->GetParent(); michael@0: const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); michael@0: bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == michael@0: groupVis->mVisible); michael@0: isCollapsed = collapseCol || collapseGroup; michael@0: if (!isCollapsed) michael@0: space += aTableFrame.GetColumnWidth(colX); michael@0: } michael@0: if (!isCollapsed && aTableFrame.ColumnHasCellSpacingBefore(colX)) { michael@0: space += aCellSpacingX; michael@0: } michael@0: } michael@0: } michael@0: return space; michael@0: } michael@0: michael@0: // subtract the heights of aRow's prev in flows from the unpaginated height michael@0: static michael@0: nscoord CalcHeightFromUnpaginatedHeight(nsPresContext* aPresContext, michael@0: nsTableRowFrame& aRow) michael@0: { michael@0: nscoord height = 0; michael@0: nsTableRowFrame* firstInFlow = michael@0: static_cast(aRow.FirstInFlow()); michael@0: if (firstInFlow->HasUnpaginatedHeight()) { michael@0: height = firstInFlow->GetUnpaginatedHeight(aPresContext); michael@0: for (nsIFrame* prevInFlow = aRow.GetPrevInFlow(); prevInFlow; michael@0: prevInFlow = prevInFlow->GetPrevInFlow()) { michael@0: height -= prevInFlow->GetSize().height; michael@0: } michael@0: } michael@0: return std::max(height, 0); michael@0: } michael@0: michael@0: nsresult michael@0: nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsTableFrame& aTableFrame, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: // XXXldb Should we be checking constrained height instead? michael@0: const bool isPaginated = aPresContext->IsPaginated(); michael@0: const bool borderCollapse = aTableFrame.IsBorderCollapse(); michael@0: nsresult rv = NS_OK; michael@0: nscoord cellSpacingX = aTableFrame.GetCellSpacingX(); michael@0: int32_t cellColSpan = 1; // must be defined here so it's set properly for non-cell kids michael@0: michael@0: nsTableIterator iter(*this); michael@0: // remember the col index of the previous cell to handle rowspans into this row michael@0: int32_t firstPrevColIndex = (iter.IsLeftToRight()) ? -1 : aTableFrame.GetColCount(); michael@0: int32_t prevColIndex = firstPrevColIndex; michael@0: nscoord x = 0; // running total of children x offset michael@0: michael@0: // This computes the max of all cell heights michael@0: nscoord cellMaxHeight = 0; michael@0: michael@0: // Reflow each of our existing cell frames michael@0: for (nsIFrame* kidFrame = iter.First(); kidFrame; kidFrame = iter.Next()) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); michael@0: if (!cellFrame) { michael@0: // XXXldb nsCSSFrameConstructor needs to enforce this! michael@0: NS_NOTREACHED("yikes, a non-row child"); michael@0: michael@0: // it's an unknown frame type, give it a generic reflow and ignore the results michael@0: nsTableCellReflowState kidReflowState(aPresContext, aReflowState, michael@0: kidFrame, nsSize(0,0), michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(*aPresContext, nsSize(0,0), false, kidReflowState); michael@0: nsHTMLReflowMetrics desiredSize(aReflowState); michael@0: nsReflowStatus status; michael@0: ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState, 0, 0, 0, status); michael@0: kidFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); michael@0: michael@0: continue; michael@0: } michael@0: michael@0: // See if we should only reflow the dirty child frames michael@0: bool doReflowChild = true; michael@0: if (!aReflowState.ShouldReflowAllKids() && michael@0: !aTableFrame.IsGeometryDirty() && michael@0: !NS_SUBTREE_DIRTY(kidFrame)) { michael@0: if (!aReflowState.mFlags.mSpecialHeightReflow) michael@0: doReflowChild = false; michael@0: } michael@0: else if ((NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight())) { michael@0: // We don't reflow a rowspan >1 cell here with a constrained height. michael@0: // That happens in nsTableRowGroupFrame::SplitSpanningCells. michael@0: if (aTableFrame.GetEffectiveRowSpan(*cellFrame) > 1) { michael@0: doReflowChild = false; michael@0: } michael@0: } michael@0: if (aReflowState.mFlags.mSpecialHeightReflow) { michael@0: if (!isPaginated && !(cellFrame->GetStateBits() & michael@0: NS_FRAME_CONTAINS_RELATIVE_HEIGHT)) { michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: int32_t cellColIndex; michael@0: cellFrame->GetColIndex(cellColIndex); michael@0: cellColSpan = aTableFrame.GetEffectiveColSpan(*cellFrame); michael@0: michael@0: // If the adjacent cell is in a prior row (because of a rowspan) add in the space michael@0: if ((iter.IsLeftToRight() && (prevColIndex != (cellColIndex - 1))) || michael@0: (!iter.IsLeftToRight() && (prevColIndex != cellColIndex + cellColSpan))) { michael@0: x += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, aTableFrame, michael@0: cellSpacingX, iter.IsLeftToRight(), false); michael@0: } michael@0: michael@0: // remember the rightmost (ltr) or leftmost (rtl) column this cell spans into michael@0: prevColIndex = (iter.IsLeftToRight()) ? cellColIndex + (cellColSpan - 1) : cellColIndex; michael@0: michael@0: // Reflow the child frame michael@0: nsRect kidRect = kidFrame->GetRect(); michael@0: nsRect kidVisualOverflow = kidFrame->GetVisualOverflowRect(); michael@0: bool firstReflow = michael@0: (kidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0; michael@0: michael@0: if (doReflowChild) { michael@0: // Calculate the available width for the table cell using the known column widths michael@0: nscoord availCellWidth = michael@0: CalcAvailWidth(aTableFrame, *cellFrame, cellSpacingX); michael@0: michael@0: nsHTMLReflowMetrics desiredSize(aReflowState); michael@0: michael@0: // If the avail width is not the same as last time we reflowed the cell or michael@0: // the cell wants to be bigger than what was available last time or michael@0: // it is a style change reflow or we are printing, then we must reflow the michael@0: // cell. Otherwise we can skip the reflow. michael@0: // XXXldb Why is this condition distinct from doReflowChild above? michael@0: nsSize cellDesiredSize = cellFrame->GetDesiredSize(); michael@0: if ((availCellWidth != cellFrame->GetPriorAvailWidth()) || michael@0: (cellDesiredSize.width > cellFrame->GetPriorAvailWidth()) || michael@0: (GetStateBits() & NS_FRAME_IS_DIRTY) || michael@0: isPaginated || michael@0: NS_SUBTREE_DIRTY(cellFrame) || michael@0: // See if it needs a special reflow, or if it had one that we need to undo. michael@0: (cellFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) || michael@0: HasPctHeight()) { michael@0: // Reflow the cell to fit the available width, height michael@0: // XXX The old IR_ChildIsDirty code used availCellWidth here. michael@0: nsSize kidAvailSize(availCellWidth, aReflowState.AvailableHeight()); michael@0: michael@0: // Reflow the child michael@0: nsTableCellReflowState kidReflowState(aPresContext, aReflowState, michael@0: kidFrame, kidAvailSize, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(*aPresContext, kidAvailSize, borderCollapse, michael@0: kidReflowState); michael@0: michael@0: nsReflowStatus status; michael@0: rv = ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState, michael@0: x, 0, 0, status); michael@0: michael@0: // allow the table to determine if/how the table needs to be rebalanced michael@0: // If any of the cells are not complete, then we're not complete michael@0: if (NS_FRAME_IS_NOT_COMPLETE(status)) { michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: } michael@0: } michael@0: else { michael@0: if (x != kidRect.x) { michael@0: kidFrame->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: desiredSize.Width() = cellDesiredSize.width; michael@0: desiredSize.Height() = cellDesiredSize.height; michael@0: desiredSize.mOverflowAreas = cellFrame->GetOverflowAreas(); michael@0: michael@0: // if we are in a floated table, our position is not yet established, so we cannot reposition our views michael@0: // the containing block will do this for us after positioning the table michael@0: if (!aTableFrame.IsFloating()) { michael@0: // Because we may have moved the frame we need to make sure any views are michael@0: // positioned properly. We have to do this, because any one of our parent michael@0: // frames could have moved and we have no way of knowing... michael@0: nsTableFrame::RePositionViews(kidFrame); michael@0: } michael@0: } michael@0: michael@0: if (NS_UNCONSTRAINEDSIZE == aReflowState.AvailableHeight()) { michael@0: if (!GetPrevInFlow()) { michael@0: // Calculate the cell's actual height given its pass2 height. This michael@0: // function takes into account the specified height (in the style) michael@0: CalculateCellActualHeight(cellFrame, desiredSize.Height()); michael@0: } michael@0: // height may have changed, adjust descent to absorb any excess difference michael@0: nscoord ascent; michael@0: if (!kidFrame->GetFirstPrincipalChild()->GetFirstPrincipalChild()) michael@0: ascent = desiredSize.Height(); michael@0: else michael@0: ascent = ((nsTableCellFrame *)kidFrame)->GetCellBaseline(); michael@0: nscoord descent = desiredSize.Height() - ascent; michael@0: UpdateHeight(desiredSize.Height(), ascent, descent, &aTableFrame, cellFrame); michael@0: } michael@0: else { michael@0: cellMaxHeight = std::max(cellMaxHeight, desiredSize.Height()); michael@0: int32_t rowSpan = aTableFrame.GetEffectiveRowSpan((nsTableCellFrame&)*kidFrame); michael@0: if (1 == rowSpan) { michael@0: SetContentHeight(cellMaxHeight); michael@0: } michael@0: } michael@0: michael@0: // Place the child michael@0: desiredSize.Width() = availCellWidth; michael@0: michael@0: FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr, x, 0, 0); michael@0: michael@0: nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidVisualOverflow, michael@0: firstReflow); michael@0: michael@0: x += desiredSize.Width(); michael@0: } michael@0: else { michael@0: if (kidRect.x != x) { michael@0: // Invalidate the old position michael@0: kidFrame->InvalidateFrameSubtree(); michael@0: // move to the new position michael@0: kidFrame->SetPosition(nsPoint(x, kidRect.y)); michael@0: nsTableFrame::RePositionViews(kidFrame); michael@0: // invalidate the new position michael@0: kidFrame->InvalidateFrameSubtree(); michael@0: } michael@0: // we need to account for the cell's width even if it isn't reflowed michael@0: x += kidRect.width; michael@0: michael@0: if (kidFrame->GetNextInFlow()) { michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: } michael@0: } michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); michael@0: x += cellSpacingX; michael@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: if (aReflowState.mFlags.mSpecialHeightReflow) { michael@0: aDesiredSize.Height() = mRect.height; michael@0: } michael@0: else if (NS_UNCONSTRAINEDSIZE == aReflowState.AvailableHeight()) { michael@0: aDesiredSize.Height() = CalcHeight(aReflowState); michael@0: if (GetPrevInFlow()) { michael@0: nscoord height = CalcHeightFromUnpaginatedHeight(aPresContext, *this); michael@0: aDesiredSize.Height() = std::max(aDesiredSize.Height(), height); michael@0: } michael@0: else { michael@0: if (isPaginated && HasStyleHeight()) { michael@0: // set the unpaginated height so next in flows can try to honor it michael@0: SetHasUnpaginatedHeight(true); michael@0: SetUnpaginatedHeight(aPresContext, aDesiredSize.Height()); michael@0: } michael@0: if (isPaginated && HasUnpaginatedHeight()) { michael@0: aDesiredSize.Height() = std::max(aDesiredSize.Height(), GetUnpaginatedHeight(aPresContext)); michael@0: } michael@0: } michael@0: } michael@0: else { // constrained height, paginated michael@0: // Compute the height we should have from style (subtracting the michael@0: // height from our prev-in-flows from the style height) michael@0: nscoord styleHeight = CalcHeightFromUnpaginatedHeight(aPresContext, *this); michael@0: if (styleHeight > aReflowState.AvailableHeight()) { michael@0: styleHeight = aReflowState.AvailableHeight(); michael@0: NS_FRAME_SET_INCOMPLETE(aStatus); michael@0: } michael@0: aDesiredSize.Height() = std::max(cellMaxHeight, styleHeight); michael@0: } michael@0: aDesiredSize.UnionOverflowAreasWithDesiredBounds(); michael@0: FinishAndStoreOverflow(&aDesiredSize); michael@0: return rv; michael@0: } michael@0: michael@0: /** Layout the entire row. michael@0: * This method stacks cells horizontally according to HTML 4.0 rules. michael@0: */ michael@0: nsresult michael@0: nsTableRowFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsTableRowFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: const nsStyleVisibility* rowVis = StyleVisibility(); michael@0: bool collapseRow = (NS_STYLE_VISIBILITY_COLLAPSE == rowVis->mVisible); michael@0: if (collapseRow) { michael@0: tableFrame->SetNeedToCollapse(true); michael@0: } 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: // See if we have a cell with specified/pct height michael@0: InitHasCellWithStyleHeight(tableFrame); michael@0: michael@0: rv = ReflowChildren(aPresContext, aDesiredSize, aReflowState, *tableFrame, michael@0: aStatus); michael@0: michael@0: if (aPresContext->IsPaginated() && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && michael@0: ShouldAvoidBreakInside(aReflowState)) { michael@0: aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@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: // 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: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * This function is called by the row group frame's SplitRowGroup() code when michael@0: * pushing a row frame that has cell frames that span into it. The cell frame michael@0: * should be reflowed with the specified height michael@0: */ michael@0: nscoord michael@0: nsTableRowFrame::ReflowCellFrame(nsPresContext* aPresContext, michael@0: const nsHTMLReflowState& aReflowState, michael@0: bool aIsTopOfPage, michael@0: nsTableCellFrame* aCellFrame, michael@0: nscoord aAvailableHeight, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: // Reflow the cell frame with the specified height. Use the existing width michael@0: nsRect cellRect = aCellFrame->GetRect(); michael@0: nsRect cellVisualOverflow = aCellFrame->GetVisualOverflowRect(); michael@0: michael@0: nsSize availSize(cellRect.width, aAvailableHeight); michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this); michael@0: bool borderCollapse = tableFrame->IsBorderCollapse(); michael@0: nsTableCellReflowState cellReflowState(aPresContext, aReflowState, michael@0: aCellFrame, availSize, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(*aPresContext, availSize, borderCollapse, cellReflowState); michael@0: cellReflowState.mFlags.mIsTopOfPage = aIsTopOfPage; michael@0: michael@0: nsHTMLReflowMetrics desiredSize(aReflowState); michael@0: michael@0: ReflowChild(aCellFrame, aPresContext, desiredSize, cellReflowState, michael@0: 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); michael@0: bool fullyComplete = NS_FRAME_IS_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus); michael@0: if (fullyComplete) { michael@0: desiredSize.Height() = aAvailableHeight; michael@0: } michael@0: aCellFrame->SetSize(nsSize(cellRect.width, desiredSize.Height())); michael@0: michael@0: // Note: VerticallyAlignChild can affect the overflow rect. michael@0: // XXX What happens if this cell has 'vertical-align: baseline' ? michael@0: // XXX Why is it assumed that the cell's ascent hasn't changed ? michael@0: if (fullyComplete) { michael@0: aCellFrame->VerticallyAlignChild(mMaxCellAscent); michael@0: } michael@0: michael@0: nsTableFrame::InvalidateTableFrame(aCellFrame, cellRect, michael@0: cellVisualOverflow, michael@0: (aCellFrame->GetStateBits() & michael@0: NS_FRAME_FIRST_REFLOW) != 0); michael@0: michael@0: aCellFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); michael@0: michael@0: return desiredSize.Height(); michael@0: } michael@0: michael@0: nscoord michael@0: nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset, michael@0: nscoord aWidth, michael@0: bool aCollapseGroup, michael@0: bool& aDidCollapse) michael@0: { michael@0: const nsStyleVisibility* rowVis = StyleVisibility(); michael@0: bool collapseRow = (NS_STYLE_VISIBILITY_COLLAPSE == rowVis->mVisible); michael@0: nsTableFrame* tableFrame = static_cast( michael@0: nsTableFrame::GetTableFrame(this)->FirstInFlow()); michael@0: if (collapseRow) { michael@0: tableFrame->SetNeedToCollapse(true); michael@0: } michael@0: michael@0: if (aRowOffset != 0) { michael@0: // We're moving, so invalidate our old position michael@0: InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: nsRect rowRect = GetRect(); michael@0: nsRect oldRect = rowRect; michael@0: nsRect oldVisualOverflow = GetVisualOverflowRect(); michael@0: michael@0: rowRect.y -= aRowOffset; michael@0: rowRect.width = aWidth; michael@0: nsOverflowAreas overflow; michael@0: nscoord shift = 0; michael@0: nscoord cellSpacingX = tableFrame->GetCellSpacingX(); michael@0: nscoord cellSpacingY = tableFrame->GetCellSpacingY(); michael@0: michael@0: if (aCollapseGroup || collapseRow) { michael@0: nsTableCellFrame* cellFrame = GetFirstCell(); michael@0: aDidCollapse = true; michael@0: shift = rowRect.height + cellSpacingY; michael@0: while (cellFrame) { michael@0: nsRect cRect = cellFrame->GetRect(); michael@0: // If aRowOffset != 0, there's no point in invalidating the cells, since michael@0: // we've already invalidated our overflow area. Note that we _do_ still michael@0: // need to invalidate if our row is not moving, because the cell might michael@0: // span out of this row, so invalidating our row rect won't do enough. michael@0: if (aRowOffset == 0) { michael@0: InvalidateFrame(); michael@0: } michael@0: cRect.height = 0; michael@0: cellFrame->SetRect(cRect); michael@0: cellFrame = cellFrame->GetNextCell(); michael@0: } michael@0: rowRect.height = 0; michael@0: } michael@0: else { // row is not collapsed michael@0: nsTableIterator iter(*this); michael@0: // remember the col index of the previous cell to handle rowspans into this michael@0: // row michael@0: int32_t firstPrevColIndex = (iter.IsLeftToRight()) ? -1 : michael@0: tableFrame->GetColCount(); michael@0: int32_t prevColIndex = firstPrevColIndex; michael@0: nscoord x = 0; // running total of children x offset michael@0: michael@0: int32_t colIncrement = iter.IsLeftToRight() ? 1 : -1; michael@0: michael@0: //nscoord x = cellSpacingX; michael@0: michael@0: nsIFrame* kidFrame = iter.First(); michael@0: while (kidFrame) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); michael@0: if (cellFrame) { michael@0: int32_t cellColIndex; michael@0: cellFrame->GetColIndex(cellColIndex); michael@0: int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame); michael@0: michael@0: // If the adjacent cell is in a prior row (because of a rowspan) add in michael@0: // the space michael@0: if ((iter.IsLeftToRight() && (prevColIndex != (cellColIndex - 1))) || michael@0: (!iter.IsLeftToRight() && michael@0: (prevColIndex != cellColIndex + cellColSpan))) { michael@0: x += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, michael@0: *tableFrame, cellSpacingX, iter.IsLeftToRight(), michael@0: true); michael@0: } michael@0: nsRect cRect(x, 0, 0, rowRect.height); michael@0: michael@0: // remember the rightmost (ltr) or leftmost (rtl) column this cell michael@0: // spans into michael@0: prevColIndex = (iter.IsLeftToRight()) ? michael@0: cellColIndex + (cellColSpan - 1) : cellColIndex; michael@0: int32_t startIndex = (iter.IsLeftToRight()) ? michael@0: cellColIndex : cellColIndex + (cellColSpan - 1); michael@0: int32_t actualColSpan = cellColSpan; michael@0: bool isVisible = false; michael@0: for (int32_t colX = startIndex; actualColSpan > 0; michael@0: colX += colIncrement, actualColSpan--) { michael@0: michael@0: nsTableColFrame* colFrame = tableFrame->GetColFrame(colX); michael@0: const nsStyleVisibility* colVis = colFrame->StyleVisibility(); michael@0: bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == michael@0: colVis->mVisible); michael@0: nsIFrame* cgFrame = colFrame->GetParent(); michael@0: const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); michael@0: bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == michael@0: groupVis->mVisible); michael@0: bool isCollapsed = collapseCol || collapseGroup; michael@0: if (!isCollapsed) { michael@0: cRect.width += tableFrame->GetColumnWidth(colX); michael@0: isVisible = true; michael@0: if ((actualColSpan > 1)) { michael@0: nsTableColFrame* nextColFrame = michael@0: tableFrame->GetColFrame(colX + colIncrement); michael@0: const nsStyleVisibility* nextColVis = michael@0: nextColFrame->StyleVisibility(); michael@0: if ( (NS_STYLE_VISIBILITY_COLLAPSE != nextColVis->mVisible) && michael@0: tableFrame->ColumnHasCellSpacingBefore(colX + colIncrement)) { michael@0: cRect.width += cellSpacingX; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: x += cRect.width; michael@0: if (isVisible) michael@0: x += cellSpacingX; michael@0: int32_t actualRowSpan = tableFrame->GetEffectiveRowSpan(*cellFrame); michael@0: nsTableRowFrame* rowFrame = GetNextRow(); michael@0: for (actualRowSpan--; actualRowSpan > 0 && rowFrame; actualRowSpan--) { michael@0: const nsStyleVisibility* nextRowVis = rowFrame->StyleVisibility(); michael@0: bool collapseNextRow = (NS_STYLE_VISIBILITY_COLLAPSE == michael@0: nextRowVis->mVisible); michael@0: if (!collapseNextRow) { michael@0: nsRect nextRect = rowFrame->GetRect(); michael@0: cRect.height += nextRect.height + cellSpacingY; michael@0: } michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: michael@0: nsRect oldCellRect = cellFrame->GetRect(); michael@0: nsRect oldCellVisualOverflow = cellFrame->GetVisualOverflowRect(); michael@0: michael@0: if (aRowOffset == 0 && cRect.TopLeft() != oldCellRect.TopLeft()) { michael@0: // We're moving the cell. Invalidate the old overflow area michael@0: cellFrame->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: cellFrame->SetRect(cRect); michael@0: michael@0: // XXXbz This looks completely bogus in the cases when we didn't michael@0: // collapse the cell! michael@0: nsRect cellBounds(0, 0, cRect.width, cRect.height); michael@0: nsOverflowAreas cellOverflow(cellBounds, cellBounds); michael@0: cellFrame->FinishAndStoreOverflow(cellOverflow, cRect.Size()); michael@0: nsTableFrame::RePositionViews(cellFrame); michael@0: ConsiderChildOverflow(overflow, cellFrame); michael@0: michael@0: if (aRowOffset == 0) { michael@0: nsTableFrame::InvalidateTableFrame(cellFrame, oldCellRect, michael@0: oldCellVisualOverflow, michael@0: false); michael@0: } michael@0: } michael@0: kidFrame = iter.Next(); // Get the next child michael@0: } michael@0: } michael@0: michael@0: SetRect(rowRect); michael@0: overflow.UnionAllWith(nsRect(0, 0, rowRect.width, rowRect.height)); michael@0: FinishAndStoreOverflow(overflow, rowRect.Size()); michael@0: michael@0: nsTableFrame::RePositionViews(this); michael@0: nsTableFrame::InvalidateTableFrame(this, oldRect, oldVisualOverflow, false); michael@0: return shift; michael@0: } michael@0: michael@0: /* michael@0: * The following method is called by the row group frame's SplitRowGroup() michael@0: * when it creates a continuing cell frame and wants to insert it into the michael@0: * row's child list. michael@0: */ michael@0: void michael@0: nsTableRowFrame::InsertCellFrame(nsTableCellFrame* aFrame, michael@0: int32_t aColIndex) michael@0: { michael@0: // Find the cell frame where col index < aColIndex michael@0: nsTableCellFrame* priorCell = nullptr; michael@0: for (nsIFrame* child = mFrames.FirstChild(); child; michael@0: child = child->GetNextSibling()) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(child); michael@0: if (cellFrame) { michael@0: int32_t colIndex; michael@0: cellFrame->GetColIndex(colIndex); michael@0: if (colIndex < aColIndex) { michael@0: priorCell = cellFrame; michael@0: } michael@0: else break; michael@0: } michael@0: } michael@0: mFrames.InsertFrame(this, priorCell, aFrame); michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsTableRowFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::tableRowFrame; michael@0: } michael@0: michael@0: nsTableRowFrame* michael@0: nsTableRowFrame::GetNextRow() const michael@0: { michael@0: nsIFrame* childFrame = GetNextSibling(); michael@0: while (childFrame) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(childFrame); michael@0: if (rowFrame) { michael@0: NS_ASSERTION(NS_STYLE_DISPLAY_TABLE_ROW == childFrame->StyleDisplay()->mDisplay, "wrong display type on rowframe"); michael@0: return rowFrame; michael@0: } michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(RowUnpaginatedHeightProperty, nullptr) michael@0: michael@0: void michael@0: nsTableRowFrame::SetUnpaginatedHeight(nsPresContext* aPresContext, michael@0: nscoord aValue) michael@0: { michael@0: NS_ASSERTION(!GetPrevInFlow(), "program error"); michael@0: // Get the property michael@0: aPresContext->PropertyTable()-> michael@0: Set(this, RowUnpaginatedHeightProperty(), NS_INT32_TO_PTR(aValue)); michael@0: } michael@0: michael@0: nscoord michael@0: nsTableRowFrame::GetUnpaginatedHeight(nsPresContext* aPresContext) michael@0: { michael@0: FrameProperties props = FirstInFlow()->Properties(); michael@0: return NS_PTR_TO_INT32(props.Get(RowUnpaginatedHeightProperty())); michael@0: } michael@0: michael@0: void nsTableRowFrame::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_TOP: michael@0: mTopContBorderWidth = 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 arg"); michael@0: } michael@0: } michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsTableRowFrame::AccessibleType() michael@0: { michael@0: return a11y::eHTMLTableRowType; michael@0: } michael@0: #endif michael@0: /** michael@0: * Sets the NS_ROW_HAS_CELL_WITH_STYLE_HEIGHT bit to indicate whether michael@0: * this row has any cells that have non-auto-height. (Row-spanning michael@0: * cells are ignored.) michael@0: */ michael@0: void nsTableRowFrame::InitHasCellWithStyleHeight(nsTableFrame* aTableFrame) michael@0: { michael@0: nsTableIterator iter(*this); michael@0: michael@0: for (nsIFrame* kidFrame = iter.First(); kidFrame; kidFrame = iter.Next()) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); michael@0: if (!cellFrame) { michael@0: NS_NOTREACHED("Table row has a non-cell child."); michael@0: continue; michael@0: } michael@0: // Ignore row-spanning cells michael@0: const nsStyleCoord &cellHeight = cellFrame->StylePosition()->mHeight; michael@0: if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 && michael@0: cellHeight.GetUnit() != eStyleUnit_Auto && michael@0: /* calc() with percentages treated like 'auto' */ michael@0: (!cellHeight.IsCalcUnit() || !cellHeight.HasPercent())) { michael@0: AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_HEIGHT); michael@0: return; michael@0: } michael@0: } michael@0: RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_HEIGHT); michael@0: } michael@0: michael@0: void michael@0: nsTableRowFrame::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: nsTableRowFrame::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: } michael@0: michael@0: /* ----- global methods ----- */ michael@0: michael@0: nsIFrame* michael@0: NS_NewTableRowFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsTableRowFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsTableRowFrame) michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsTableRowFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("TableRow"), aResult); michael@0: } michael@0: #endif