From e4bac7857eaa625177347293f314f866c746db22 Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Tue, 19 Aug 2014 18:24:58 -0700 Subject: [PATCH] Bug 35168 (Part 2) - Allow relative positioning of internal table objects. r=dbaron --- layout/base/RestyleManager.cpp | 14 ++- layout/tables/nsTableFrame.cpp | 134 ++++++++++++++----------- layout/tables/nsTableFrame.h | 1 + layout/tables/nsTablePainter.cpp | 120 +++++++++++++++++----- layout/tables/nsTablePainter.h | 29 +++++- layout/tables/nsTableRowFrame.cpp | 52 +++++++--- layout/tables/nsTableRowGroupFrame.cpp | 71 +++++++------ layout/tables/nsTableRowGroupFrame.h | 1 + 8 files changed, 283 insertions(+), 139 deletions(-) diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 759e06e4a622..267afdda4d61 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -356,16 +356,14 @@ RestyleManager::RecomputePosition(nsIFrame* aFrame) // For relative positioning, we can simply update the frame rect if (display->IsRelativelyPositionedStyle()) { - if (display->IsInnerTableStyle()) { - // We don't currently support relative positioning of inner table - // elements (bug 35168). If we apply offsets to things we haven't - // previously offset, we'll get confused. So bail. - return true; - } - - // Move the frame if (display->mPosition == NS_STYLE_POSITION_STICKY) { + if (display->IsInnerTableStyle()) { + // We don't currently support sticky positioning of inner table + // elements (bug 975644). Bail. + return true; + } + // Update sticky positioning for an entire element at once, starting with // the first continuation or ib-split sibling. // It's rare that the frame we already have isn't already the first diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp index a44e49c13ac4..a91a77405214 100644 --- a/layout/tables/nsTableFrame.cpp +++ b/layout/tables/nsTableFrame.cpp @@ -1845,7 +1845,7 @@ nsTableFrame::Reflow(nsPresContext* aPresContext, // if there is an incomplete child, then set the desired height to include it but not the next one nsMargin borderPadding = GetChildAreaOffset(&aReflowState); aDesiredSize.Height() = borderPadding.bottom + GetCellSpacingY(GetRowCount()) + - lastChildReflowed->GetRect().YMost(); + lastChildReflowed->GetNormalRect().YMost(); } haveDesiredHeight = true; @@ -2649,6 +2649,7 @@ nsTableFrame::InitChildReflowState(nsHTMLReflowState& aReflowState) // aKidRect is relative to the upper-left origin of our frame void nsTableFrame::PlaceChild(nsTableReflowState& aReflowState, nsIFrame* aKidFrame, + nsPoint aKidPosition, nsHTMLReflowMetrics& aKidDesiredSize, const nsRect& aOriginalKidRect, const nsRect& aOriginalKidVisualOverflow) @@ -2658,7 +2659,7 @@ void nsTableFrame::PlaceChild(nsTableReflowState& aReflowState, // Place and size the child FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr, - aReflowState.x, aReflowState.y, 0); + aKidPosition.x, aKidPosition.y, 0); InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow, isFirstReflow); @@ -2842,7 +2843,10 @@ nsTableFrame::PlaceRepeatedFooter(nsTableReflowState& aReflowState, desiredSize.ClearSize(); ReflowChild(aTfoot, presContext, desiredSize, footerReflowState, aReflowState.x, aReflowState.y, 0, footerStatus); - PlaceChild(aReflowState, aTfoot, desiredSize, origTfootRect, + nsPoint kidPosition(aReflowState.x, aReflowState.y); + footerReflowState.ApplyRelativePositioning(&kidPosition); + + PlaceChild(aReflowState, aTfoot, kidPosition, desiredSize, origTfootRect, origTfootVisualOverflow); } @@ -2962,7 +2966,7 @@ nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, // We ignore a repeated head row group in this check to avoid causing // infinite loops in some circumstances - see bug 344883. if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) && - (rowGroups[childX - 1]->GetRect().YMost() > 0)) { + (rowGroups[childX - 1]->GetNormalRect().YMost() > 0)) { kidReflowState.mFlags.mIsTopOfPage = false; } aReflowState.y += cellSpacingY; @@ -2977,6 +2981,8 @@ nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, ReflowChild(kidFrame, presContext, desiredSize, kidReflowState, aReflowState.x, aReflowState.y, 0, aStatus); + nsPoint kidPosition(aReflowState.x, aReflowState.y); + kidReflowState.ApplyRelativePositioning(&kidPosition); if (reorder) { // reorder row groups the reflow may have changed the nextinflows @@ -3008,8 +3014,8 @@ nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, if (childX+1 < rowGroups.Length()) { nsIFrame* nextRowGroupFrame = rowGroups[childX + 1]; if (nextRowGroupFrame) { - PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, - oldKidVisualOverflow); + PlaceChild(aReflowState, kidFrame, kidPosition, desiredSize, + oldKidRect, oldKidVisualOverflow); if (allowRepeatedFooter) { PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); } @@ -3037,8 +3043,8 @@ nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, break; } else { // we can't push so lets make clear how much space we need - PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, - oldKidVisualOverflow); + PlaceChild(aReflowState, kidFrame, kidPosition, desiredSize, + oldKidRect, oldKidVisualOverflow); aLastChildReflowed = kidFrame; if (allowRepeatedFooter) { PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); @@ -3061,7 +3067,7 @@ nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, } // Place the child - PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, + PlaceChild(aReflowState, kidFrame, kidPosition, desiredSize, oldKidRect, oldKidVisualOverflow); // Remember where we just were in case we end up pushing children @@ -3108,12 +3114,12 @@ nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, } else { // it isn't being reflowed aReflowState.y += cellSpacingY; - nsRect kidRect = kidFrame->GetRect(); + nsRect kidRect = kidFrame->GetNormalRect(); if (kidRect.y != aReflowState.y) { // invalidate the old position kidFrame->InvalidateFrameSubtree(); - kidRect.y = aReflowState.y; - kidFrame->SetRect(kidRect); // move to the new position + // move to the new position + kidFrame->MovePositionBy(nsPoint(0, aReflowState.y - kidRect.y)); RePositionViews(kidFrame); // invalidate the new position kidFrame->InvalidateFrameSubtree(); @@ -3266,64 +3272,64 @@ nsTableFrame::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; nscoord amountUsedByRG = 0; nscoord yOriginRow = 0; - nsRect rgRect = rgFrame->GetRect(); + nsRect rgNormalRect = rgFrame->GetNormalRect(); if (!rgFrame->HasStyleHeight()) { nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); while (rowFrame) { - nsRect rowRect = rowFrame->GetRect(); + nsRect rowNormalRect = rowFrame->GetNormalRect(); nscoord cellSpacingY = GetCellSpacingY(rowFrame->GetRowIndex()); if ((amountUsed < aAmount) && rowFrame->HasPctHeight()) { nscoord pctHeight = rowFrame->GetHeight(pctBasis); - nscoord amountForRow = std::min(aAmount - amountUsed, pctHeight - rowRect.height); + nscoord amountForRow = std::min(aAmount - amountUsed, + pctHeight - rowNormalRect.height); if (amountForRow > 0) { - nsRect oldRowRect = rowRect; - rowRect.height += amountForRow; - // XXXbz we don't need to change rowRect.y to be yOriginRow? - rowFrame->SetRect(rowRect); - yOriginRow += rowRect.height + cellSpacingY; - yEndRG += rowRect.height + cellSpacingY; + // XXXbz we don't need to move the row's y position to yOriginRow? + nsRect origRowRect = rowFrame->GetRect(); + nscoord newRowHeight = rowNormalRect.height + amountForRow; + rowFrame->SetSize(nsSize(rowNormalRect.width, newRowHeight)); + yOriginRow += newRowHeight + cellSpacingY; + yEndRG += newRowHeight + cellSpacingY; amountUsed += amountForRow; amountUsedByRG += amountForRow; //rowFrame->DidResize(); nsTableFrame::RePositionViews(rowFrame); - rgFrame->InvalidateFrameWithRect(oldRowRect); + rgFrame->InvalidateFrameWithRect(origRowRect); rgFrame->InvalidateFrame(); } } else { - if (amountUsed > 0 && yOriginRow != rowRect.y && + if (amountUsed > 0 && yOriginRow != rowNormalRect.y && !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { rowFrame->InvalidateFrameSubtree(); - rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow)); + rowFrame->MovePositionBy(nsPoint(0, yOriginRow - rowNormalRect.y)); nsTableFrame::RePositionViews(rowFrame); rowFrame->InvalidateFrameSubtree(); } - yOriginRow += rowRect.height + cellSpacingY; - yEndRG += rowRect.height + cellSpacingY; + yOriginRow += rowNormalRect.height + cellSpacingY; + yEndRG += rowNormalRect.height + cellSpacingY; } rowFrame = rowFrame->GetNextRow(); } if (amountUsed > 0) { - if (rgRect.y != yOriginRG) { + if (rgNormalRect.y != yOriginRG) { rgFrame->InvalidateFrameSubtree(); } - nsRect origRgRect = rgRect; + nsRect origRgNormalRect = rgFrame->GetRect(); nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect(); - rgRect.y = yOriginRG; - rgRect.height += amountUsedByRG; + rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y)); + rgFrame->SetSize(nsSize(rgNormalRect.width, + rgNormalRect.height + amountUsedByRG)); - rgFrame->SetRect(rgRect); - - nsTableFrame::InvalidateTableFrame(rgFrame, origRgRect, + nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect, origRgVisualOverflow, false); } } - else if (amountUsed > 0 && yOriginRG != rgRect.y) { + else if (amountUsed > 0 && yOriginRG != rgNormalRect.y) { rgFrame->InvalidateFrameSubtree(); - rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG)); + rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y)); // Make sure child views are properly positioned nsTableFrame::RePositionViews(rgFrame); rgFrame->InvalidateFrameSubtree(); @@ -3403,14 +3409,14 @@ nsTableFrame::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; nscoord amountUsedByRG = 0; nscoord yOriginRow = 0; - nsRect rgRect = rgFrame->GetRect(); + nsRect rgNormalRect = rgFrame->GetNormalRect(); nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect(); // see if there is an eligible row group or we distribute to all rows if (!firstUnStyledRG || !rgFrame->HasStyleHeight() || !eligibleRows) { nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); while (rowFrame) { nscoord cellSpacingY = GetCellSpacingY(rowFrame->GetRowIndex()); - nsRect rowRect = rowFrame->GetRect(); + nsRect rowNormalRect = rowFrame->GetNormalRect(); nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); // see if there is an eligible row or we distribute to all rows if (!firstUnStyledRow || !rowFrame->HasStyleHeight() || !eligibleRows) { @@ -3419,7 +3425,7 @@ nsTableFrame::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, if (!expandEmptyRows) { // The amount of additional space each row gets is proportional to // its height - ratio = float(rowRect.height) / float(divisor); + ratio = float(rowNormalRect.height) / float(divisor); } else { // empty rows get all the same additional space ratio = 1.0f / float(eligibleRows); @@ -3435,17 +3441,18 @@ nsTableFrame::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, ? aAmount - amountUsed : NSToCoordRound(((float)(heightToDistribute)) * ratio); amountForRow = std::min(amountForRow, aAmount - amountUsed); - if (yOriginRow != rowRect.y) { + if (yOriginRow != rowNormalRect.y) { rowFrame->InvalidateFrameSubtree(); } // update the row height - nsRect newRowRect(rowRect.x, yOriginRow, rowRect.width, - rowRect.height + amountForRow); - rowFrame->SetRect(newRowRect); + nsRect origRowRect = rowFrame->GetRect(); + nscoord newRowHeight = rowNormalRect.height + amountForRow; + rowFrame->MovePositionBy(nsPoint(0, yOriginRow - rowNormalRect.y)); + rowFrame->SetSize(nsSize(rowNormalRect.width, newRowHeight)); - yOriginRow += newRowRect.height + cellSpacingY; - yEndRG += newRowRect.height + cellSpacingY; + yOriginRow += newRowHeight + cellSpacingY; + yEndRG += newRowHeight + cellSpacingY; amountUsed += amountForRow; amountUsedByRG += amountForRow; @@ -3453,37 +3460,39 @@ nsTableFrame::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, //rowFrame->DidResize(); nsTableFrame::RePositionViews(rowFrame); - nsTableFrame::InvalidateTableFrame(rowFrame, rowRect, rowVisualOverflow, - false); + nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect, + rowVisualOverflow, false); } else { - if (amountUsed > 0 && yOriginRow != rowRect.y) { + if (amountUsed > 0 && yOriginRow != rowNormalRect.y) { rowFrame->InvalidateFrameSubtree(); - rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow)); + rowFrame->MovePositionBy(nsPoint(0, yOriginRow - rowNormalRect.y)); nsTableFrame::RePositionViews(rowFrame); rowFrame->InvalidateFrameSubtree(); } - yOriginRow += rowRect.height + cellSpacingY; - yEndRG += rowRect.height + cellSpacingY; + yOriginRow += rowNormalRect.height + cellSpacingY; + yEndRG += rowNormalRect.height + cellSpacingY; } rowFrame = rowFrame->GetNextRow(); } if (amountUsed > 0) { - if (rgRect.y != yOriginRG) { + if (rgNormalRect.y != yOriginRG) { rgFrame->InvalidateFrameSubtree(); } - rgFrame->SetRect(nsRect(rgRect.x, yOriginRG, rgRect.width, - rgRect.height + amountUsedByRG)); + nsRect origRgNormalRect = rgFrame->GetRect(); + rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y)); + rgFrame->SetSize(nsSize(rgNormalRect.width, + rgNormalRect.height + amountUsedByRG)); - nsTableFrame::InvalidateTableFrame(rgFrame, rgRect, rgVisualOverflow, - false); + nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect, + rgVisualOverflow, false); } // Make sure child views are properly positioned } - else if (amountUsed > 0 && yOriginRG != rgRect.y) { + else if (amountUsed > 0 && yOriginRG != rgNormalRect.y) { rgFrame->InvalidateFrameSubtree(); - rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG)); + rgFrame->MovePositionBy(nsPoint(0, yOriginRG - rgNormalRect.y)); // Make sure child views are properly positioned nsTableFrame::RePositionViews(rgFrame); rgFrame->InvalidateFrameSubtree(); @@ -3583,8 +3592,15 @@ nsTableFrame::GetLogicalBaseline(WritingMode aWritingMode) const nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; if (rgFrame->GetRowCount()) { firstRow = rgFrame->GetFirstRow(); - ascent = rgFrame->BStart(aWritingMode, containerWidth) + - firstRow->BStart(aWritingMode, containerWidth) + + + nscoord rgNormalBStart = + LogicalRect(aWritingMode, rgFrame->GetNormalRect(), containerWidth) + .Origin(aWritingMode).B(aWritingMode); + nscoord firstRowNormalBStart = + LogicalRect(aWritingMode, firstRow->GetNormalRect(), containerWidth) + .Origin(aWritingMode).B(aWritingMode); + + ascent = rgNormalBStart + firstRowNormalBStart + firstRow->GetRowBaseline(aWritingMode); break; } diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h index 4f4e8655f28c..6efaa4656ad6 100644 --- a/layout/tables/nsTableFrame.h +++ b/layout/tables/nsTableFrame.h @@ -666,6 +666,7 @@ protected: void PlaceChild(nsTableReflowState& aReflowState, nsIFrame* aKidFrame, + nsPoint aKidPosition, nsHTMLReflowMetrics& aKidDesiredSize, const nsRect& aOriginalKidRect, const nsRect& aOriginalKidVisualOverflow); diff --git a/layout/tables/nsTablePainter.cpp b/layout/tables/nsTablePainter.cpp index 7281245ae645..82db2dbf4aec 100644 --- a/layout/tables/nsTablePainter.cpp +++ b/layout/tables/nsTablePainter.cpp @@ -92,11 +92,7 @@ Elements with stacking contexts set up their own painter to finish the painting process, since they were skipped. They call the appropriate sub-part of the loop (e.g. PaintRow) which will paint the frame and - descendants. Note that it is permissible according to CSS2.1 to ignore' - 'position:relative' (and implicitly, 'opacity') on table parts so that - table parts can never create stacking contexts; if we want to, we can - implement that, and then we won't have to deal with TableBackgroundPainter - being used anywhere but from the nsTableFrame. + descendants. XXX views are going */ @@ -422,7 +418,15 @@ TableBackgroundPainter::PaintTable(nsTableFrame* aTableFrame, // Need to compute the right rect via GetOffsetTo, since the row // group may not be a child of the table. mRowGroup.mRect.MoveTo(rg->GetOffsetTo(aTableFrame)); - if (mRowGroup.mRect.Intersects(mDirtyRect - mRenderPt)) { + + // We have to draw backgrounds not only within the overflow region of this + // row group, but also possibly (in the case of column / column group + // backgrounds) at its pre-relative-positioning location. + nsRect rgVisualOverflow = rg->GetVisualOverflowRectRelativeToSelf(); + nsRect rgOverflowRect = rgVisualOverflow + rg->GetPosition(); + nsRect rgNormalRect = rgVisualOverflow + rg->GetNormalPosition(); + + if (rgOverflowRect.Union(rgNormalRect).Intersects(mDirtyRect - mRenderPt)) { nsresult rv = PaintRowGroup(rg, rg->IsPseudoStackingContextFromStyle()); if (NS_FAILED(rv)) return rv; } @@ -470,16 +474,12 @@ TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame, mRowGroup.mRect.MoveTo(0, 0); /* Find the right row to start with */ - nscoord ignored; // We don't care about overflow above, since what we really - // care about are backgrounds and overflow above doesn't - // correspond to backgrounds, since cells can't span up from - // their originating row. We do care about overflow below, - // however, since that can be due to rowspans. // Note that mDirtyRect - mRenderPt is guaranteed to be in the row // group's coordinate system here, so passing its .y to // GetFirstRowContaining is ok. - nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &ignored); + nscoord overflowAbove; + nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &overflowAbove); // Sadly, it seems like there may be non-row frames in there... or something? // There are certainly null-checks in GetFirstRow() and GetNextRow(). :( @@ -500,9 +500,12 @@ TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame, /* Finally paint */ for (; row; row = row->GetNextRow()) { mRow.SetFrame(row); - if (mDirtyRect.YMost() - mRenderPt.y < mRow.mRect.y) { // Intersect wouldn't handle - // rowspans. + // Be sure to consider our positions both pre- and post-relative + // positioning, since we potentially need to paint at both places. + nscoord rowY = std::min(mRow.mRect.y, row->GetNormalPosition().y); + // Intersect wouldn't handle rowspans. + if ((mDirtyRect.YMost() - mRenderPt.y) <= (rowY - overflowAbove)) { // All done; cells originating in later rows can't intersect mDirtyRect. break; } @@ -564,10 +567,20 @@ TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame, //else: Use row group's coord system -> no translation necessary for (nsTableCellFrame* cell = aFrame->GetFirstCell(); cell; cell = cell->GetNextCell()) { - //Translate to use the same coord system as mRow. - mCellRect = cell->GetRect() + mRow.mRect.TopLeft() + mRenderPt; - if (mCellRect.Intersects(mDirtyRect)) { - nsresult rv = PaintCell(cell, aPassThrough || cell->IsPseudoStackingContextFromStyle()); + nsRect cellBGRect, rowBGRect, rowGroupBGRect, colBGRect; + ComputeCellBackgrounds(cell, cellBGRect, rowBGRect, + rowGroupBGRect, colBGRect); + + // Find the union of all the cell background layers. + nsRect combinedRect(cellBGRect); + combinedRect.UnionRect(combinedRect, rowBGRect); + combinedRect.UnionRect(combinedRect, rowGroupBGRect); + combinedRect.UnionRect(combinedRect, colBGRect); + + if (combinedRect.Intersects(mDirtyRect)) { + bool passCell = aPassThrough || cell->IsPseudoStackingContextFromStyle(); + nsresult rv = PaintCell(cell, cellBGRect, rowBGRect, rowGroupBGRect, + colBGRect, passCell); if (NS_FAILED(rv)) return rv; } } @@ -579,7 +592,11 @@ TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame, nsresult TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, - bool aPassSelf) + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect, + bool aPassSelf) { NS_PRECONDITION(aCell, "null frame"); @@ -604,7 +621,7 @@ TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, mCols[colIndex].mColGroup->mRect + mRenderPt, mCols[colIndex].mColGroup->mFrame->StyleContext(), *mCols[colIndex].mColGroup->mBorder, - mBGPaintFlags, &mCellRect); + mBGPaintFlags, &aColBGRect); } //Paint column background @@ -614,7 +631,7 @@ TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, mCols[colIndex].mCol.mRect + mRenderPt, mCols[colIndex].mCol.mFrame->StyleContext(), *mCols[colIndex].mCol.mBorder, - mBGPaintFlags, &mCellRect); + mBGPaintFlags, &aColBGRect); } //Paint row group background @@ -624,7 +641,7 @@ TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, mRowGroup.mRect + mRenderPt, mRowGroup.mFrame->StyleContext(), *mRowGroup.mBorder, - mBGPaintFlags, &mCellRect); + mBGPaintFlags, &aRowGroupBGRect); } //Paint row background @@ -634,14 +651,69 @@ TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, mRow.mRect + mRenderPt, mRow.mFrame->StyleContext(), *mRow.mBorder, - mBGPaintFlags, &mCellRect); + mBGPaintFlags, &aRowBGRect); } //Paint cell background in border-collapse unless we're just passing if (mIsBorderCollapse && !aPassSelf) { aCell->PaintCellBackground(mRenderingContext, mDirtyRect, - mCellRect.TopLeft(), mBGPaintFlags); + aCellBGRect.TopLeft(), mBGPaintFlags); } return NS_OK; } + +void +TableBackgroundPainter::ComputeCellBackgrounds(nsTableCellFrame* aCell, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect) +{ + // We need to compute table background layer rects for this cell space, + // adjusted for possible relative positioning. This behavior is not specified + // at the time of this writing, but the approach below should be web + // compatible. + // + // Our goal is that relative positioning of a table part should leave + // backgrounds *under* that part unchanged. ("Under" being defined by CSS 2.1 + // Section 17.5.1.) If a cell is positioned, we do not expect the row + // background to move. On the other hand, the backgrounds of layers *above* + // the positioned part are taken along for the ride -- for example, + // positioning a row group will also cause the row background to be drawn in + // the new location, unless it has further positioning applied. + // + // Each table part layer has its position stored in the coordinate space of + // the layer below (which is to say, its geometric parent), and the stored + // position is the post-relative-positioning one. The position of each + // background layer rect is thus determined by peeling off successive table + // part layers, removing the contribution of each layer's positioning one by + // one. Every rect we generate will be the same size, the size of the cell + // space. + + // We cannot rely on the row group background data to be available, since some + // callers enter through PaintRow. + nsIFrame* rowGroupFrame = + mRowGroup.mFrame ? mRowGroup.mFrame : mRow.mFrame->GetParent(); + + // The cell background goes at the cell's position, translated to use the same + // coordinate system as mRow. + aCellBGRect = aCell->GetRect() + mRow.mRect.TopLeft() + mRenderPt; + + // The row background goes at the normal position of the cell, which is to say + // the position without relative positioning applied. + aRowBGRect = aCellBGRect + (aCell->GetNormalPosition() - aCell->GetPosition()); + + // The row group background goes at the position we'd find the cell if neither + // the cell's relative positioning nor the row's were applied. + aRowGroupBGRect = aRowBGRect + + (mRow.mFrame->GetNormalPosition() - mRow.mFrame->GetPosition()); + + // The column and column group backgrounds (they're always at the same + // location, since relative positioning doesn't apply to columns or column + // groups) are drawn at the position we'd find the cell if none of the cell's, + // row's, or row group's relative positioning were applied. + aColBGRect = aRowGroupBGRect + + (rowGroupFrame->GetNormalPosition() - rowGroupFrame->GetPosition()); + +} diff --git a/layout/tables/nsTablePainter.h b/layout/tables/nsTablePainter.h index bc00642a370b..73d2874a09ac 100644 --- a/layout/tables/nsTablePainter.h +++ b/layout/tables/nsTablePainter.h @@ -131,12 +131,34 @@ class TableBackgroundPainter /** Paint table background layers for this cell space * Also paints cell's own background in border-collapse mode - * @param aFrame - the cell - * @param aPassSelf - pass this cell; i.e. paint only underlying layers + * @param aCell - the cell + * @param aCellBGRect - background rect for the cell + * @param aRowBGRect - background rect for the row + * @param aRowGroupBGRect - background rect for the row group + * @param aColBGRect - background rect for the column and column group + * @param aPassSelf - pass this cell; i.e. paint only underlying layers */ - nsresult PaintCell(nsTableCellFrame* aFrame, + nsresult PaintCell(nsTableCellFrame* aCell, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect, bool aPassSelf); + /** Compute table background layer positions for this cell space + * @param aCell - the cell + * @param aCellBGRectOut - outparam: background rect for the cell + * @param aRowBGRectOut - outparam: background rect for the row + * @param aRowGroupBGRectOut - outparam: background rect for the row group + * @param aColBGRectOut - outparam: background rect for the column + and column group + */ + void ComputeCellBackgrounds(nsTableCellFrame* aCell, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect); + /** Translate mRenderingContext, mDirtyRect, and mCols' column and * colgroup coords * @param aDX - origin's x-coord change @@ -214,7 +236,6 @@ class TableBackgroundPainter uint32_t mNumCols; TableBackgroundData mRowGroup; //current row group TableBackgroundData mRow; //current row - nsRect mCellRect; //current cell's rect nsStyleBorder mZeroBorder; //cached zero-width border uint32_t mBGPaintFlags; diff --git a/layout/tables/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp index 843f167ece94..841e9b7423aa 100644 --- a/layout/tables/nsTableRowFrame.cpp +++ b/layout/tables/nsTableRowFrame.cpp @@ -2,6 +2,9 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Maybe.h" + #include "nsTableRowFrame.h" #include "nsTableRowGroupFrame.h" #include "nsIPresShell.h" @@ -395,7 +398,7 @@ nscoord nsTableRowFrame::GetRowBaseline(WritingMode aWritingMode) while (childFrame) { if (IS_TABLE_CELL(childFrame->GetType())) { nsIFrame* firstKid = childFrame->GetFirstPrincipalChild(); - ascent = std::max(ascent, firstKid->GetRect().YMost()); + ascent = std::max(ascent, firstKid->GetNormalRect().YMost()); } // Get the next child childFrame = iter.Next(); @@ -861,7 +864,10 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, // Reflow the child frame nsRect kidRect = kidFrame->GetRect(); + nsPoint origKidNormalPosition = kidFrame->GetNormalPosition(); + MOZ_ASSERT(origKidNormalPosition.y == 0); nsRect kidVisualOverflow = kidFrame->GetVisualOverflowRect(); + nsPoint kidPosition(x, 0); bool firstReflow = (kidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0; @@ -870,6 +876,7 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, nscoord availCellWidth = CalcAvailWidth(aTableFrame, *cellFrame); + Maybe kidReflowState; nsHTMLReflowMetrics desiredSize(aReflowState); // If the avail width is not the same as last time we reflowed the cell or @@ -890,19 +897,18 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, HasPctHeight()) { // Reflow the cell to fit the available width, height // XXX The old IR_ChildIsDirty code used availCellWidth here. - nsSize kidAvailSize(availCellWidth, aReflowState.AvailableHeight()); + nsSize kidAvailSize(availCellWidth, aReflowState.AvailableHeight()); // Reflow the child - nsTableCellReflowState - kidReflowState(aPresContext, aReflowState, kidFrame, - LogicalSize(kidFrame->GetWritingMode(), - kidAvailSize), - nsHTMLReflowState::CALLER_WILL_INIT); + kidReflowState.emplace(aPresContext, aReflowState, kidFrame, + LogicalSize(kidFrame->GetWritingMode(), + kidAvailSize), + nsHTMLReflowState::CALLER_WILL_INIT); InitChildReflowState(*aPresContext, kidAvailSize, borderCollapse, - kidReflowState); + *kidReflowState); nsReflowStatus status; - ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState, + ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowState, x, 0, 0, status); // allow the table to determine if/how the table needs to be rebalanced @@ -912,7 +918,7 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, } } else { - if (x != kidRect.x) { + if (x != origKidNormalPosition.x) { kidFrame->InvalidateFrameSubtree(); } @@ -957,7 +963,18 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, // Place the child desiredSize.ISize(rowWM) = availCellWidth; - FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr, x, 0, 0); + if (kidReflowState) { + // We reflowed. Apply relative positioning in the normal way. + kidReflowState->ApplyRelativePositioning(&kidPosition); + } else { + // We didn't reflow. To take relative positioning into account, + // translate the new position by the vector from the previous 'normal' + // position to the previous position. + // XXX(seth): This doesn't work for 'position: sticky'. + kidPosition += kidRect.TopLeft() - origKidNormalPosition; + } + FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr, + kidPosition.x, kidPosition.y, 0); nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidVisualOverflow, firstReflow); @@ -965,11 +982,12 @@ nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, x += desiredSize.Width(); } else { - if (kidRect.x != x) { + if (x != origKidNormalPosition.x) { // Invalidate the old position kidFrame->InvalidateFrameSubtree(); - // move to the new position - kidFrame->SetPosition(nsPoint(x, kidRect.y)); + // Move to the new position. As above, we need to account for relative + // positioning. + kidFrame->MovePositionBy(nsPoint(x - origKidNormalPosition.x, 0)); nsTableFrame::RePositionViews(kidFrame); // invalidate the new position kidFrame->InvalidateFrameSubtree(); @@ -1259,14 +1277,16 @@ nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset, } nsRect oldCellRect = cellFrame->GetRect(); + nsPoint oldCellNormalPos = cellFrame->GetNormalPosition(); nsRect oldCellVisualOverflow = cellFrame->GetVisualOverflowRect(); - if (aRowOffset == 0 && cRect.TopLeft() != oldCellRect.TopLeft()) { + if (aRowOffset == 0 && cRect.TopLeft() != oldCellNormalPos) { // We're moving the cell. Invalidate the old overflow area cellFrame->InvalidateFrameSubtree(); } - cellFrame->SetRect(cRect); + cellFrame->MovePositionBy(cRect.TopLeft() - oldCellNormalPos); + cellFrame->SetSize(cRect.Size()); // XXXbz This looks completely bogus in the cases when we didn't // collapse the cell! diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp index 09838eb3a5a8..47e0a2559630 100644 --- a/layout/tables/nsTableRowGroupFrame.cpp +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -205,7 +205,8 @@ DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, if (kid) { // have a cursor, use it while (kid) { - if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost()) + if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost() && + kid->GetNormalRect().y - overflowAbove >= aDirtyRect.YMost()) break; f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); kid = kid->GetNextSibling(); @@ -277,6 +278,7 @@ void nsTableRowGroupFrame::PlaceChild(nsPresContext* aPresContext, nsRowGroupReflowState& aReflowState, nsIFrame* aKidFrame, + nsPoint aKidPosition, nsHTMLReflowMetrics& aDesiredSize, const nsRect& aOriginalKidRect, const nsRect& aOriginalKidVisualOverflow) @@ -285,8 +287,8 @@ nsTableRowGroupFrame::PlaceChild(nsPresContext* aPresContext, (aKidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0; // Place and size the child - FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr, 0, - aReflowState.y, 0); + FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr, + aKidPosition.x, aKidPosition.y, 0); nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow, isFirstReflow); @@ -399,16 +401,18 @@ nsTableRowGroupFrame::ReflowChildren(nsPresContext* aPresContext, "If we're not on the first frame, we should have a " "previous sibling..."); // If prev row has nonzero YMost, then we can't be at the top of the page - if (prevKidFrame && prevKidFrame->GetRect().YMost() > 0) { + if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) { kidReflowState.mFlags.mIsTopOfPage = false; } ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState, 0, aReflowState.y, 0, aStatus); + nsPoint kidPosition(0, aReflowState.y); + kidReflowState.ApplyRelativePositioning(&kidPosition); // Place the child - PlaceChild(aPresContext, aReflowState, kidFrame, desiredSize, - oldKidRect, oldKidVisualOverflow); + PlaceChild(aPresContext, aReflowState, kidFrame, kidPosition, + desiredSize, oldKidRect, oldKidVisualOverflow); aReflowState.y += cellSpacingY; if (!reflowAllKids) { @@ -422,8 +426,6 @@ nsTableRowGroupFrame::ReflowChildren(nsPresContext* aPresContext, unit != eStyleUnit_Coord) { // Because other cells in the row may need to be aligned // differently, repaint the entire row - nsRect kidRect(0, aReflowState.y, - desiredSize.Width(), desiredSize.Height()); InvalidateFrame(); } else if (oldKidRect.height != desiredSize.Height()) @@ -548,7 +550,7 @@ nsTableRowGroupFrame::CalculateRowHeights(nsPresContext* aPresContext, if (!startRowFrame) return; // the current row group height is the y origin of the 1st row we are about to calculated a height for - nscoord startRowGroupHeight = startRowFrame->GetPosition().y; + nscoord startRowGroupHeight = startRowFrame->GetNormalPosition().y; int32_t numRows = GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex()); // collect the current height of each row. nscoord* rowHeights = nullptr; @@ -777,25 +779,26 @@ nsTableRowGroupFrame::CalculateRowHeights(nsPresContext* aPresContext, for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) { nsRect rowBounds = rowFrame->GetRect(); nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); + nscoord deltaY = yOrigin - rowFrame->GetNormalPosition().y; - bool movedFrame = (rowBounds.y != yOrigin); nscoord rowHeight = (rowInfo[rowIndex].height > 0) ? rowInfo[rowIndex].height : 0; - if (movedFrame || (rowHeight != rowBounds.height)) { + if (deltaY != 0 || (rowHeight != rowBounds.height)) { // Resize/move the row to its final size and position - if (movedFrame) { + if (deltaY != 0) { rowFrame->InvalidateFrameSubtree(); } - rowFrame->SetRect(nsRect(rowBounds.x, yOrigin, rowBounds.width, - rowHeight)); + rowFrame->MovePositionBy(nsPoint(0, deltaY)); + rowFrame->SetSize(nsSize(rowBounds.width, rowHeight)); nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowVisualOverflow, false); - } - if (movedFrame) { - nsTableFrame::RePositionViews(rowFrame); - // XXXbz we don't need to update our overflow area? + + if (deltaY != 0) { + nsTableFrame::RePositionViews(rowFrame); + // XXXbz we don't need to update our overflow area? + } } yOrigin += rowHeight + tableFrame->GetCellSpacingY(startRowIndex + rowIndex); } @@ -868,11 +871,12 @@ nsTableRowGroupFrame::SlideChild(nsRowGroupReflowState& aReflowState, nsIFrame* aKidFrame) { // Move the frame if we need to - nsPoint oldPosition = aKidFrame->GetPosition(); + nsPoint oldPosition = aKidFrame->GetNormalPosition(); nsPoint newPosition = oldPosition; newPosition.y = aReflowState.y; if (oldPosition.y != newPosition.y) { aKidFrame->InvalidateFrameSubtree(); + aReflowState.reflowState.ApplyRelativePositioning(&newPosition); aKidFrame->SetPosition(newPosition); nsTableFrame::RePositionViews(aKidFrame); aKidFrame->InvalidateFrameSubtree(); @@ -926,7 +930,7 @@ nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext, for (nsTableRowFrame* row = &aFirstRow; !wasLast; row = row->GetNextRow()) { wasLast = (row == &aLastRow); int32_t rowIndex = row->GetRowIndex(); - nsPoint rowPos = row->GetPosition(); + nsPoint rowPos = row->GetNormalPosition(); // Iterate the cells looking for those that have rowspan > 1 for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) { int32_t rowSpan = aTable.GetEffectiveRowSpan(rowIndex, *cell); @@ -941,7 +945,7 @@ nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext, NS_ASSERTION(cellAvailHeight >= 0, "No space for cell?"); bool isTopOfPage = (row == &aFirstRow) && aFirstRowIsTopOfPage; - nsRect rowRect = row->GetRect(); + nsRect rowRect = row->GetNormalRect(); nsSize rowAvailSize(aReflowState.AvailableWidth(), std::max(aReflowState.AvailableHeight() - rowRect.y, 0)); @@ -991,7 +995,7 @@ nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext, } } if (!haveRowSpan) { - aDesiredHeight = aLastRow.GetRect().YMost(); + aDesiredHeight = aLastRow.GetNormalRect().YMost(); } } @@ -1072,7 +1076,7 @@ nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame; rowFrame = rowFrame->GetNextRow()) { bool rowIsOnPage = true; nscoord cellSpacingY = aTableFrame->GetCellSpacingY(rowFrame->GetRowIndex()); - nsRect rowRect = rowFrame->GetRect(); + nsRect rowRect = rowFrame->GetNormalRect(); // See if the row fits on this page if (rowRect.YMost() > availHeight) { nsTableRowFrame* contRow = nullptr; @@ -1178,7 +1182,7 @@ nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, break; } if (prevRowFrame) { - spanningRowBottom = prevRowFrame->GetRect().YMost(); + spanningRowBottom = prevRowFrame->GetNormalRect().YMost(); lastRowThisPage = prevRowFrame; isTopOfPage = (lastRowThisPage == firstRowThisPage) && aReflowState.mFlags.mIsTopOfPage; aStatus = NS_FRAME_NOT_COMPLETE; @@ -1215,7 +1219,7 @@ nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, // Try to put firstTruncateRow on the next page nsTableRowFrame* rowBefore = ::GetRowBefore(*firstRowThisPage, *firstTruncatedRow); nscoord oldSpanningRowBottom = spanningRowBottom; - spanningRowBottom = rowBefore->GetRect().YMost(); + spanningRowBottom = rowBefore->GetNormalRect().YMost(); UndoContinuedRow(aPresContext, contRow); contRow = nullptr; @@ -1886,12 +1890,12 @@ nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove) // encountering a row whose overflowArea.YMost() is <= aY but which has // a row above it containing cell(s) that span to include aY. while (cursorIndex > 0 && - cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) { + cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow > aY) { --cursorIndex; cursorFrame = property->mFrames[cursorIndex]; } while (cursorIndex + 1 < frameCount && - cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) { + cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow <= aY) { ++cursorIndex; cursorFrame = property->mFrames[cursorIndex]; } @@ -1904,7 +1908,18 @@ nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove) bool nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) { - nsRect overflowRect = aFrame->GetVisualOverflowRect(); + // Relative positioning can cause table parts to move, but we will still paint + // the backgrounds for the parts under them at their 'normal' position. That + // means that we must consider the overflow rects at both positions. For + // example, if we use relative positioning to move a row-spanning cell, we + // will still paint the row background for that cell at its normal position, + // which will overflow the row. + // XXX(seth): This probably isn't correct in the presence of transforms. + nsRect positionedOverflowRect = aFrame->GetVisualOverflowRect(); + nsPoint positionedToNormal = aFrame->GetNormalPosition() - aFrame->GetPosition(); + nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal; + + nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect); if (overflowRect.IsEmpty()) return true; nscoord overflowAbove = -overflowRect.y; diff --git a/layout/tables/nsTableRowGroupFrame.h b/layout/tables/nsTableRowGroupFrame.h index 5becf9d55e6a..c10e6a092868 100644 --- a/layout/tables/nsTableRowGroupFrame.h +++ b/layout/tables/nsTableRowGroupFrame.h @@ -339,6 +339,7 @@ protected: void PlaceChild(nsPresContext* aPresContext, nsRowGroupReflowState& aReflowState, nsIFrame* aKidFrame, + nsPoint aKidPosition, nsHTMLReflowMetrics& aDesiredSize, const nsRect& aOriginalKidRect, const nsRect& aOriginalKidVisualOverflow);