no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / tables / nsTableRowGroupFrame.cpp
blob9ae8a59a901d439caef37819d17ec60ab43840b0
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "nsTableRowGroupFrame.h"
7 #include "mozilla/ComputedStyle.h"
8 #include "mozilla/PresShell.h"
9 #include "mozilla/StaticPrefs_layout.h"
11 #include "nsCOMPtr.h"
12 #include "nsTableRowFrame.h"
13 #include "nsTableFrame.h"
14 #include "nsTableCellFrame.h"
15 #include "nsPresContext.h"
16 #include "nsStyleConsts.h"
17 #include "nsIContent.h"
18 #include "nsIFrame.h"
19 #include "nsIFrameInlines.h"
20 #include "nsGkAtoms.h"
21 #include "nsCSSRendering.h"
22 #include "nsHTMLParts.h"
23 #include "nsCSSFrameConstructor.h"
24 #include "nsDisplayList.h"
26 #include "nsCellMap.h" //table cell navigation
27 #include <algorithm>
29 using namespace mozilla;
30 using namespace mozilla::layout;
32 namespace mozilla {
34 struct TableRowGroupReflowInput final {
35 // Our reflow input
36 const ReflowInput& mReflowInput;
38 // The available size (computed from the parent)
39 LogicalSize mAvailSize;
41 // Running block-offset
42 nscoord mBCoord = 0;
44 explicit TableRowGroupReflowInput(const ReflowInput& aReflowInput)
45 : mReflowInput(aReflowInput), mAvailSize(aReflowInput.AvailableSize()) {}
47 ~TableRowGroupReflowInput() = default;
50 } // namespace mozilla
52 nsTableRowGroupFrame::nsTableRowGroupFrame(ComputedStyle* aStyle,
53 nsPresContext* aPresContext)
54 : nsContainerFrame(aStyle, aPresContext, kClassID) {
55 SetRepeatable(false);
58 nsTableRowGroupFrame::~nsTableRowGroupFrame() = default;
60 void nsTableRowGroupFrame::Destroy(DestroyContext& aContext) {
61 nsTableFrame::MaybeUnregisterPositionedTablePart(this);
62 nsContainerFrame::Destroy(aContext);
65 NS_QUERYFRAME_HEAD(nsTableRowGroupFrame)
66 NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame)
67 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
69 int32_t nsTableRowGroupFrame::GetRowCount() const {
70 #ifdef DEBUG
71 for (nsIFrame* f : mFrames) {
72 NS_ASSERTION(f->StyleDisplay()->mDisplay == mozilla::StyleDisplay::TableRow,
73 "Unexpected display");
74 NS_ASSERTION(f->IsTableRowFrame(), "Unexpected frame type");
76 #endif
78 return mFrames.GetLength();
81 int32_t nsTableRowGroupFrame::GetStartRowIndex() const {
82 int32_t result = -1;
83 if (mFrames.NotEmpty()) {
84 NS_ASSERTION(mFrames.FirstChild()->IsTableRowFrame(),
85 "Unexpected frame type");
86 result = static_cast<nsTableRowFrame*>(mFrames.FirstChild())->GetRowIndex();
88 // if the row group doesn't have any children, get it the hard way
89 if (-1 == result) {
90 return GetTableFrame()->GetStartRowIndex(this);
93 return result;
96 void nsTableRowGroupFrame::AdjustRowIndices(int32_t aRowIndex,
97 int32_t anAdjustment) {
98 for (nsIFrame* rowFrame : mFrames) {
99 if (mozilla::StyleDisplay::TableRow == rowFrame->StyleDisplay()->mDisplay) {
100 int32_t index = ((nsTableRowFrame*)rowFrame)->GetRowIndex();
101 if (index >= aRowIndex)
102 ((nsTableRowFrame*)rowFrame)->SetRowIndex(index + anAdjustment);
107 int32_t nsTableRowGroupFrame::GetAdjustmentForStoredIndex(
108 int32_t aStoredIndex) {
109 nsTableFrame* tableFrame = GetTableFrame();
110 return tableFrame->GetAdjustmentForStoredIndex(aStoredIndex);
113 void nsTableRowGroupFrame::MarkRowsAsDeleted(nsTableRowFrame& aStartRowFrame,
114 int32_t aNumRowsToDelete) {
115 nsTableRowFrame* currentRowFrame = &aStartRowFrame;
116 for (;;) {
117 // XXXneerja - Instead of calling AddDeletedRowIndex() per row frame
118 // it is possible to change AddDeleteRowIndex to instead take
119 // <start row index> and <num of rows to mark for deletion> as arguments.
120 // The problem that emerges here is mDeletedRowIndexRanges only stores
121 // disjoint index ranges and since AddDeletedRowIndex() must operate on
122 // the "stored" index, in some cases it is possible that the range
123 // of indices to delete becomes overlapping EG: Deleting rows 9 - 11 and
124 // then from the remaining rows deleting the *new* rows 7 to 20.
125 // Handling these overlapping ranges is much more complicated to
126 // implement and so I opted to add the deleted row index of one row at a
127 // time and maintain the invariant that the range of deleted row indices
128 // is always disjoint.
129 currentRowFrame->AddDeletedRowIndex();
130 if (--aNumRowsToDelete == 0) {
131 break;
133 currentRowFrame = do_QueryFrame(currentRowFrame->GetNextSibling());
134 if (!currentRowFrame) {
135 MOZ_ASSERT_UNREACHABLE("expected another row frame");
136 break;
141 void nsTableRowGroupFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) {
142 nsTableFrame* tableFrame = GetTableFrame();
143 return tableFrame->AddDeletedRowIndex(aDeletedRowStoredIndex);
146 void nsTableRowGroupFrame::InitRepeatedFrame(
147 nsTableRowGroupFrame* aHeaderFooterFrame) {
148 nsTableRowFrame* copyRowFrame = GetFirstRow();
149 nsTableRowFrame* originalRowFrame = aHeaderFooterFrame->GetFirstRow();
150 AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
151 while (copyRowFrame && originalRowFrame) {
152 copyRowFrame->AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
153 int rowIndex = originalRowFrame->GetRowIndex();
154 copyRowFrame->SetRowIndex(rowIndex);
156 // For each table cell frame set its column index
157 nsTableCellFrame* originalCellFrame = originalRowFrame->GetFirstCell();
158 nsTableCellFrame* copyCellFrame = copyRowFrame->GetFirstCell();
159 while (copyCellFrame && originalCellFrame) {
160 NS_ASSERTION(
161 originalCellFrame->GetContent() == copyCellFrame->GetContent(),
162 "cell frames have different content");
163 uint32_t colIndex = originalCellFrame->ColIndex();
164 copyCellFrame->SetColIndex(colIndex);
166 // Move to the next cell frame
167 copyCellFrame = copyCellFrame->GetNextCell();
168 originalCellFrame = originalCellFrame->GetNextCell();
171 // Move to the next row frame
172 originalRowFrame = originalRowFrame->GetNextRow();
173 copyRowFrame = copyRowFrame->GetNextRow();
177 // Handle the child-traversal part of DisplayGenericTablePart
178 static void DisplayRows(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
179 const nsDisplayListSet& aLists) {
180 nscoord overflowAbove;
181 nsTableRowGroupFrame* f = static_cast<nsTableRowGroupFrame*>(aFrame);
182 // Don't try to use the row cursor if we have to descend into placeholders;
183 // we might have rows containing placeholders, where the row's overflow
184 // area doesn't intersect the dirty rect but we need to descend into the row
185 // to see out of flows.
186 // Note that we really want to check ShouldDescendIntoFrame for all
187 // the rows in |f|, but that's exactly what we're trying to avoid, so we
188 // approximate it by checking it for |f|: if it's true for any row
189 // in |f| then it's true for |f| itself.
190 nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(f, true)
191 ? nullptr
192 : f->GetFirstRowContaining(aBuilder->GetVisibleRect().y,
193 &overflowAbove);
195 if (kid) {
196 // have a cursor, use it
197 while (kid) {
198 if (kid->GetRect().y - overflowAbove >=
199 aBuilder->GetVisibleRect().YMost()) {
200 break;
202 f->BuildDisplayListForChild(aBuilder, kid, aLists);
203 kid = kid->GetNextSibling();
205 return;
208 // No cursor. Traverse children the hard way and build a cursor while we're at
209 // it
210 nsTableRowGroupFrame::FrameCursorData* cursor = f->SetupRowCursor();
211 kid = f->PrincipalChildList().FirstChild();
212 while (kid) {
213 f->BuildDisplayListForChild(aBuilder, kid, aLists);
215 if (cursor) {
216 if (!cursor->AppendFrame(kid)) {
217 f->ClearRowCursor();
218 return;
222 kid = kid->GetNextSibling();
224 if (cursor) {
225 cursor->FinishBuildingCursor();
229 void nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
230 const nsDisplayListSet& aLists) {
231 DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground());
233 for (nsTableRowFrame* row = GetFirstRow(); row; row = row->GetNextRow()) {
234 if (!aBuilder->GetDirtyRect().Intersects(row->InkOverflowRect() +
235 row->GetNormalPosition())) {
236 continue;
238 row->PaintCellBackgroundsForFrame(this, aBuilder, aLists,
239 row->GetNormalPosition());
242 DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground());
244 DisplayOutline(aBuilder, aLists);
246 DisplayRows(aBuilder, this, aLists);
249 LogicalSides nsTableRowGroupFrame::GetLogicalSkipSides() const {
250 LogicalSides skip(mWritingMode);
251 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
252 StyleBoxDecorationBreak::Clone)) {
253 return skip;
256 if (GetPrevInFlow()) {
257 skip |= eLogicalSideBitsBStart;
259 if (GetNextInFlow()) {
260 skip |= eLogicalSideBitsBEnd;
262 return skip;
265 // Position and size aKidFrame and update our reflow input.
266 void nsTableRowGroupFrame::PlaceChild(
267 nsPresContext* aPresContext, TableRowGroupReflowInput& aReflowInput,
268 nsIFrame* aKidFrame, const ReflowInput& aKidReflowInput, WritingMode aWM,
269 const LogicalPoint& aKidPosition, const nsSize& aContainerSize,
270 ReflowOutput& aDesiredSize, const nsRect& aOriginalKidRect,
271 const nsRect& aOriginalKidInkOverflow) {
272 bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
274 // Place and size the child
275 FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, &aKidReflowInput,
276 aWM, aKidPosition, aContainerSize,
277 ReflowChildFlags::ApplyRelativePositioning);
279 nsTableFrame* tableFrame = GetTableFrame();
280 if (tableFrame->IsBorderCollapse()) {
281 nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect,
282 aOriginalKidInkOverflow, isFirstReflow);
285 // Adjust the running block-offset
286 aReflowInput.mBCoord += aDesiredSize.BSize(aWM);
288 // If our block-size is constrained then update the available bsize
289 if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(aWM)) {
290 aReflowInput.mAvailSize.BSize(aWM) -= aDesiredSize.BSize(aWM);
294 void nsTableRowGroupFrame::InitChildReflowInput(nsPresContext* aPresContext,
295 bool aBorderCollapse,
296 ReflowInput& aReflowInput) {
297 const auto childWM = aReflowInput.GetWritingMode();
298 LogicalMargin border(childWM);
299 if (aBorderCollapse) {
300 auto* rowFrame = static_cast<nsTableRowFrame*>(aReflowInput.mFrame);
301 border = rowFrame->GetBCBorderWidth(childWM);
303 const LogicalMargin zeroPadding(childWM);
304 aReflowInput.Init(aPresContext, Nothing(), Some(border), Some(zeroPadding));
307 static void CacheRowBSizesForPrinting(nsTableRowFrame* aFirstRow,
308 WritingMode aWM) {
309 for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) {
310 if (!row->GetPrevInFlow()) {
311 row->SetUnpaginatedBSize(row->BSize(aWM));
316 void nsTableRowGroupFrame::ReflowChildren(
317 nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
318 TableRowGroupReflowInput& aReflowInput, nsReflowStatus& aStatus,
319 bool* aPageBreakBeforeEnd) {
320 if (aPageBreakBeforeEnd) {
321 *aPageBreakBeforeEnd = false;
324 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode();
325 nsTableFrame* tableFrame = GetTableFrame();
326 const bool borderCollapse = tableFrame->IsBorderCollapse();
328 // XXXldb Should we really be checking IsPaginated(),
329 // or should we *only* check available block-size?
330 // (Think about multi-column layout!)
331 bool isPaginated = aPresContext->IsPaginated() &&
332 NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm);
334 bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() ||
335 tableFrame->IsGeometryDirty() ||
336 tableFrame->NeedToCollapse();
338 // in vertical-rl mode, we always need the row bsizes in order to
339 // get the necessary containerSize for placing our kids
340 bool needToCalcRowBSizes = reflowAllKids || wm.IsVerticalRL();
342 nsSize containerSize =
343 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained();
345 nsIFrame* prevKidFrame = nullptr;
346 for (nsTableRowFrame* kidFrame = GetFirstRow(); kidFrame;
347 prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextRow()) {
348 const nscoord rowSpacing =
349 tableFrame->GetRowSpacing(kidFrame->GetRowIndex());
351 // Reflow the row frame
352 if (reflowAllKids || kidFrame->IsSubtreeDirty() ||
353 (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow &&
354 (isPaginated ||
355 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
356 LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize);
357 nsRect oldKidInkOverflow = kidFrame->InkOverflowRect();
359 ReflowOutput kidDesiredSize(aReflowInput.mReflowInput);
361 // Reflow the child into the available space, giving it as much bsize as
362 // it wants. We'll deal with splitting later after we've computed the row
363 // bsizes, taking into account cells with row spans...
364 LogicalSize kidAvailSize = aReflowInput.mAvailSize;
365 kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
366 ReflowInput kidReflowInput(aPresContext, aReflowInput.mReflowInput,
367 kidFrame, kidAvailSize, Nothing(),
368 ReflowInput::InitFlag::CallerWillInit);
369 InitChildReflowInput(aPresContext, borderCollapse, kidReflowInput);
371 // This can indicate that columns were resized.
372 if (aReflowInput.mReflowInput.IsIResize()) {
373 kidReflowInput.SetIResize(true);
376 NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame,
377 "If we're not on the first frame, we should have a "
378 "previous sibling...");
379 // If prev row has nonzero YMost, then we can't be at the top of the page
380 if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) {
381 kidReflowInput.mFlags.mIsTopOfPage = false;
384 LogicalPoint kidPosition(wm, 0, aReflowInput.mBCoord);
385 ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, wm,
386 kidPosition, containerSize, ReflowChildFlags::Default,
387 aStatus);
389 // Place the child
390 PlaceChild(aPresContext, aReflowInput, kidFrame, kidReflowInput, wm,
391 kidPosition, containerSize, kidDesiredSize,
392 oldKidRect.GetPhysicalRect(wm, containerSize),
393 oldKidInkOverflow);
394 aReflowInput.mBCoord += rowSpacing;
396 if (!reflowAllKids) {
397 if (IsSimpleRowFrame(tableFrame, kidFrame)) {
398 // Inform the row of its new bsize.
399 kidFrame->DidResize();
400 // the overflow area may have changed inflate the overflow area
401 const nsStylePosition* stylePos = StylePosition();
402 if (tableFrame->IsAutoBSize(wm) &&
403 !stylePos->BSize(wm).ConvertsToLength()) {
404 // Because other cells in the row may need to be aligned
405 // differently, repaint the entire row
406 InvalidateFrame();
407 } else if (oldKidRect.BSize(wm) != kidDesiredSize.BSize(wm)) {
408 needToCalcRowBSizes = true;
410 } else {
411 needToCalcRowBSizes = true;
415 if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) {
416 nsTableRowFrame* nextRow = kidFrame->GetNextRow();
417 if (nextRow) {
418 *aPageBreakBeforeEnd =
419 nsTableFrame::PageBreakAfter(kidFrame, nextRow);
422 } else {
423 // Move a child that was skipped during a reflow.
424 const LogicalPoint oldPosition =
425 kidFrame->GetLogicalNormalPosition(wm, containerSize);
426 if (oldPosition.B(wm) != aReflowInput.mBCoord) {
427 kidFrame->InvalidateFrameSubtree();
428 const LogicalPoint offset(wm, 0,
429 aReflowInput.mBCoord - oldPosition.B(wm));
430 kidFrame->MovePositionBy(wm, offset);
431 nsTableFrame::RePositionViews(kidFrame);
432 kidFrame->InvalidateFrameSubtree();
435 // Adjust the running b-offset so we know where the next row should be
436 // placed
437 nscoord bSize = kidFrame->BSize(wm) + rowSpacing;
438 aReflowInput.mBCoord += bSize;
440 if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm)) {
441 aReflowInput.mAvailSize.BSize(wm) -= bSize;
444 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
447 if (GetFirstRow()) {
448 aReflowInput.mBCoord -=
449 tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount());
452 // Return our desired rect
453 aDesiredSize.ISize(wm) = aReflowInput.mReflowInput.AvailableISize();
454 aDesiredSize.BSize(wm) = aReflowInput.mBCoord;
456 if (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow) {
457 DidResizeRows(aDesiredSize);
458 if (isPaginated) {
459 CacheRowBSizesForPrinting(GetFirstRow(), wm);
461 } else if (needToCalcRowBSizes) {
462 CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.mReflowInput);
463 if (!reflowAllKids) {
464 InvalidateFrame();
469 nsTableRowFrame* nsTableRowGroupFrame::GetFirstRow() const {
470 nsIFrame* firstChild = mFrames.FirstChild();
471 MOZ_ASSERT(
472 !firstChild || static_cast<nsTableRowFrame*>(do_QueryFrame(firstChild)),
473 "How do we have a non-row child?");
474 return static_cast<nsTableRowFrame*>(firstChild);
477 nsTableRowFrame* nsTableRowGroupFrame::GetLastRow() const {
478 nsIFrame* lastChild = mFrames.LastChild();
479 MOZ_ASSERT(
480 !lastChild || static_cast<nsTableRowFrame*>(do_QueryFrame(lastChild)),
481 "How do we have a non-row child?");
482 return static_cast<nsTableRowFrame*>(lastChild);
485 struct RowInfo {
486 RowInfo() { bSize = pctBSize = hasStyleBSize = hasPctBSize = isSpecial = 0; }
487 unsigned bSize; // content bsize or fixed bsize, excluding pct bsize
488 unsigned pctBSize : 29; // pct bsize
489 unsigned hasStyleBSize : 1;
490 unsigned hasPctBSize : 1;
491 unsigned isSpecial : 1; // there is no cell originating in the row with
492 // rowspan=1 and there are at least 2 cells spanning
493 // the row and there is no style bsize on the row
496 static void UpdateBSizes(RowInfo& aRowInfo, nscoord aAdditionalBSize,
497 nscoord& aTotal, nscoord& aUnconstrainedTotal) {
498 aRowInfo.bSize += aAdditionalBSize;
499 aTotal += aAdditionalBSize;
500 if (!aRowInfo.hasStyleBSize) {
501 aUnconstrainedTotal += aAdditionalBSize;
505 void nsTableRowGroupFrame::DidResizeRows(ReflowOutput& aDesiredSize) {
506 // Update the cells spanning rows with their new bsizes.
507 // This is the place where all of the cells in the row get set to the bsize
508 // of the row.
509 // Reset the overflow area.
510 aDesiredSize.mOverflowAreas.Clear();
511 for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame;
512 rowFrame = rowFrame->GetNextRow()) {
513 rowFrame->DidResize();
514 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame);
518 // This calculates the bsize of all the rows and takes into account
519 // style bsize on the row group, style bsizes on rows and cells, style bsizes on
520 // rowspans. Actual row bsizes will be adjusted later if the table has a style
521 // bsize. Even if rows don't change bsize, this method must be called to set the
522 // bsizes of each cell in the row to the bsize of its row.
523 void nsTableRowGroupFrame::CalculateRowBSizes(nsPresContext* aPresContext,
524 ReflowOutput& aDesiredSize,
525 const ReflowInput& aReflowInput) {
526 nsTableFrame* tableFrame = GetTableFrame();
527 const bool isPaginated = aPresContext->IsPaginated();
529 int32_t numEffCols = tableFrame->GetEffectiveColCount();
531 int32_t startRowIndex = GetStartRowIndex();
532 // find the row corresponding to the row index we just found
533 nsTableRowFrame* startRowFrame = GetFirstRow();
535 if (!startRowFrame) {
536 return;
539 // The current row group block-size is the block-origin of the 1st row
540 // we are about to calculate a block-size for.
541 WritingMode wm = aReflowInput.GetWritingMode();
542 nsSize containerSize; // actual value is unimportant as we're initially
543 // computing sizes, not physical positions
544 nscoord startRowGroupBSize =
545 startRowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm);
547 int32_t numRows =
548 GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex());
549 // Collect the current bsize of each row.
550 if (numRows <= 0) return;
552 AutoTArray<RowInfo, 32> rowInfo;
553 // XXX(Bug 1631371) Check if this should use a fallible operation as it
554 // pretended earlier.
555 rowInfo.AppendElements(numRows);
557 bool hasRowSpanningCell = false;
558 nscoord bSizeOfRows = 0;
559 nscoord bSizeOfUnStyledRows = 0;
560 // Get the bsize of each row without considering rowspans. This will be the
561 // max of the largest desired bsize of each cell, the largest style bsize of
562 // each cell, the style bsize of the row.
563 nscoord pctBSizeBasis = GetBSizeBasis(aReflowInput);
564 int32_t
565 rowIndex; // the index in rowInfo, not among the rows in the row group
566 nsTableRowFrame* rowFrame;
567 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
568 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
569 nscoord nonPctBSize = rowFrame->GetContentBSize();
570 if (isPaginated) {
571 nonPctBSize = std::max(nonPctBSize, rowFrame->BSize(wm));
573 if (!rowFrame->GetPrevInFlow()) {
574 if (rowFrame->HasPctBSize()) {
575 rowInfo[rowIndex].hasPctBSize = true;
576 rowInfo[rowIndex].pctBSize = rowFrame->GetInitialBSize(pctBSizeBasis);
578 rowInfo[rowIndex].hasStyleBSize = rowFrame->HasStyleBSize();
579 nonPctBSize = std::max(nonPctBSize, rowFrame->GetFixedBSize());
581 UpdateBSizes(rowInfo[rowIndex], nonPctBSize, bSizeOfRows,
582 bSizeOfUnStyledRows);
584 if (!rowInfo[rowIndex].hasStyleBSize) {
585 if (isPaginated ||
586 tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) {
587 rowInfo[rowIndex].isSpecial = true;
588 // iteratate the row's cell frames to see if any do not have rowspan > 1
589 nsTableCellFrame* cellFrame = rowFrame->GetFirstCell();
590 while (cellFrame) {
591 int32_t rowSpan = tableFrame->GetEffectiveRowSpan(
592 rowIndex + startRowIndex, *cellFrame);
593 if (1 == rowSpan) {
594 rowInfo[rowIndex].isSpecial = false;
595 break;
597 cellFrame = cellFrame->GetNextCell();
601 // See if a cell spans into the row. If so we'll have to do the next step
602 if (!hasRowSpanningCell) {
603 if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) {
604 hasRowSpanningCell = true;
609 if (hasRowSpanningCell) {
610 // Get the bsize of cells with rowspans and allocate any extra space to the
611 // rows they span iteratate the child frames and process the row frames
612 // among them
613 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
614 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
615 // See if the row has an originating cell with rowspan > 1. We cannot
616 // determine this for a row in a continued row group by calling
617 // RowHasSpanningCells, because the row's fif may not have any originating
618 // cells yet the row may have a continued cell which originates in it.
619 if (GetPrevInFlow() || tableFrame->RowHasSpanningCells(
620 startRowIndex + rowIndex, numEffCols)) {
621 nsTableCellFrame* cellFrame = rowFrame->GetFirstCell();
622 // iteratate the row's cell frames
623 while (cellFrame) {
624 const nscoord rowSpacing =
625 tableFrame->GetRowSpacing(startRowIndex + rowIndex);
626 int32_t rowSpan = tableFrame->GetEffectiveRowSpan(
627 rowIndex + startRowIndex, *cellFrame);
628 if ((rowIndex + rowSpan) > numRows) {
629 // there might be rows pushed already to the nextInFlow
630 rowSpan = numRows - rowIndex;
632 if (rowSpan > 1) { // a cell with rowspan > 1, determine the bsize of
633 // the rows it spans
634 nscoord bsizeOfRowsSpanned = 0;
635 nscoord bsizeOfUnStyledRowsSpanned = 0;
636 nscoord numSpecialRowsSpanned = 0;
637 nscoord cellSpacingTotal = 0;
638 int32_t spanX;
639 for (spanX = 0; spanX < rowSpan; spanX++) {
640 bsizeOfRowsSpanned += rowInfo[rowIndex + spanX].bSize;
641 if (!rowInfo[rowIndex + spanX].hasStyleBSize) {
642 bsizeOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].bSize;
644 if (0 != spanX) {
645 cellSpacingTotal += rowSpacing;
647 if (rowInfo[rowIndex + spanX].isSpecial) {
648 numSpecialRowsSpanned++;
651 nscoord bsizeOfAreaSpanned = bsizeOfRowsSpanned + cellSpacingTotal;
652 // get the bsize of the cell
653 LogicalSize cellFrameSize = cellFrame->GetLogicalSize(wm);
654 LogicalSize cellDesSize = cellFrame->GetDesiredSize();
655 cellDesSize.BSize(wm) = rowFrame->CalcCellActualBSize(
656 cellFrame, cellDesSize.BSize(wm), wm);
657 cellFrameSize.BSize(wm) = cellDesSize.BSize(wm);
658 if (cellFrame->HasVerticalAlignBaseline()) {
659 // to ensure that a spanning cell with a long descender doesn't
660 // collide with the next row, we need to take into account the
661 // shift that will be done to align the cell on the baseline of
662 // the row.
663 cellFrameSize.BSize(wm) +=
664 rowFrame->GetMaxCellAscent() - cellFrame->GetCellBaseline();
667 if (bsizeOfAreaSpanned < cellFrameSize.BSize(wm)) {
668 // the cell's bsize is larger than the available space of the rows
669 // it spans so distribute the excess bsize to the rows affected
670 nscoord extra = cellFrameSize.BSize(wm) - bsizeOfAreaSpanned;
671 nscoord extraUsed = 0;
672 if (0 == numSpecialRowsSpanned) {
673 // NS_ASSERTION(bsizeOfRowsSpanned > 0, "invalid row span
674 // situation");
675 bool haveUnStyledRowsSpanned = (bsizeOfUnStyledRowsSpanned > 0);
676 nscoord divisor = (haveUnStyledRowsSpanned)
677 ? bsizeOfUnStyledRowsSpanned
678 : bsizeOfRowsSpanned;
679 if (divisor > 0) {
680 for (spanX = rowSpan - 1; spanX >= 0; spanX--) {
681 if (!haveUnStyledRowsSpanned ||
682 !rowInfo[rowIndex + spanX].hasStyleBSize) {
683 // The amount of additional space each row gets is
684 // proportional to its bsize
685 float percent = ((float)rowInfo[rowIndex + spanX].bSize) /
686 ((float)divisor);
688 // give rows their percentage, except for the first row
689 // which gets the remainder
690 nscoord extraForRow =
691 (0 == spanX)
692 ? extra - extraUsed
693 : NSToCoordRound(((float)(extra)) * percent);
694 extraForRow = std::min(extraForRow, extra - extraUsed);
695 // update the row bsize
696 UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow,
697 bSizeOfRows, bSizeOfUnStyledRows);
698 extraUsed += extraForRow;
699 if (extraUsed >= extra) {
700 NS_ASSERTION((extraUsed == extra),
701 "invalid row bsize calculation");
702 break;
706 } else {
707 // put everything in the last row
708 UpdateBSizes(rowInfo[rowIndex + rowSpan - 1], extra,
709 bSizeOfRows, bSizeOfUnStyledRows);
711 } else {
712 // give the extra to the special rows
713 nscoord numSpecialRowsAllocated = 0;
714 for (spanX = rowSpan - 1; spanX >= 0; spanX--) {
715 if (rowInfo[rowIndex + spanX].isSpecial) {
716 // The amount of additional space each degenerate row gets
717 // is proportional to the number of them
718 float percent = 1.0f / ((float)numSpecialRowsSpanned);
720 // give rows their percentage, except for the first row
721 // which gets the remainder
722 nscoord extraForRow =
723 (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated)
724 ? extra - extraUsed
725 : NSToCoordRound(((float)(extra)) * percent);
726 extraForRow = std::min(extraForRow, extra - extraUsed);
727 // update the row bsize
728 UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow,
729 bSizeOfRows, bSizeOfUnStyledRows);
730 extraUsed += extraForRow;
731 if (extraUsed >= extra) {
732 NS_ASSERTION((extraUsed == extra),
733 "invalid row bsize calculation");
734 break;
740 } // if (rowSpan > 1)
741 cellFrame = cellFrame->GetNextCell();
742 } // while (cellFrame)
743 } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) {
744 } // while (rowFrame)
747 // pct bsize rows have already got their content bsizes.
748 // Give them their pct bsizes up to pctBSizeBasis
749 nscoord extra = pctBSizeBasis - bSizeOfRows;
750 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0);
751 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
752 RowInfo& rInfo = rowInfo[rowIndex];
753 if (rInfo.hasPctBSize) {
754 nscoord rowExtra =
755 (rInfo.pctBSize > rInfo.bSize) ? rInfo.pctBSize - rInfo.bSize : 0;
756 rowExtra = std::min(rowExtra, extra);
757 UpdateBSizes(rInfo, rowExtra, bSizeOfRows, bSizeOfUnStyledRows);
758 extra -= rowExtra;
762 bool styleBSizeAllocation = false;
763 nscoord rowGroupBSize = startRowGroupBSize + bSizeOfRows +
764 tableFrame->GetRowSpacing(0, numRows - 1);
765 // if we have a style bsize, allocate the extra bsize to unconstrained rows
766 if ((aReflowInput.ComputedBSize() > rowGroupBSize) &&
767 (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize())) {
768 nscoord extraComputedBSize = aReflowInput.ComputedBSize() - rowGroupBSize;
769 nscoord extraUsed = 0;
770 bool haveUnStyledRows = (bSizeOfUnStyledRows > 0);
771 nscoord divisor = (haveUnStyledRows) ? bSizeOfUnStyledRows : bSizeOfRows;
772 if (divisor > 0) {
773 styleBSizeAllocation = true;
774 for (rowIndex = 0; rowIndex < numRows; rowIndex++) {
775 if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleBSize) {
776 // The amount of additional space each row gets is based on the
777 // percentage of space it occupies
778 float percent = ((float)rowInfo[rowIndex].bSize) / ((float)divisor);
779 // give rows their percentage, except for the last row which gets the
780 // remainder
781 nscoord extraForRow =
782 (numRows - 1 == rowIndex)
783 ? extraComputedBSize - extraUsed
784 : NSToCoordRound(((float)extraComputedBSize) * percent);
785 extraForRow = std::min(extraForRow, extraComputedBSize - extraUsed);
786 // update the row bsize
787 UpdateBSizes(rowInfo[rowIndex], extraForRow, bSizeOfRows,
788 bSizeOfUnStyledRows);
789 extraUsed += extraForRow;
790 if (extraUsed >= extraComputedBSize) {
791 NS_ASSERTION((extraUsed == extraComputedBSize),
792 "invalid row bsize calculation");
793 break;
798 rowGroupBSize = aReflowInput.ComputedBSize();
801 if (wm.IsVertical()) {
802 // we need the correct containerSize below for block positioning in
803 // vertical-rl writing mode
804 containerSize.width = rowGroupBSize;
807 nscoord bOrigin = startRowGroupBSize;
808 // update the rows with their (potentially) new bsizes
809 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
810 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
811 nsRect rowBounds = rowFrame->GetRect();
812 LogicalSize rowBoundsSize(wm, rowBounds.Size());
813 nsRect rowInkOverflow = rowFrame->InkOverflowRect();
814 nscoord deltaB =
815 bOrigin - rowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm);
817 nscoord rowBSize =
818 (rowInfo[rowIndex].bSize > 0) ? rowInfo[rowIndex].bSize : 0;
820 if (deltaB != 0 || (rowBSize != rowBoundsSize.BSize(wm))) {
821 // Resize/move the row to its final size and position
822 if (deltaB != 0) {
823 rowFrame->InvalidateFrameSubtree();
826 rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, deltaB));
827 rowFrame->SetSize(LogicalSize(wm, rowBoundsSize.ISize(wm), rowBSize));
829 nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowInkOverflow,
830 false);
832 if (deltaB != 0) {
833 nsTableFrame::RePositionViews(rowFrame);
834 // XXXbz we don't need to update our overflow area?
837 bOrigin += rowBSize + tableFrame->GetRowSpacing(startRowIndex + rowIndex);
840 if (isPaginated && styleBSizeAllocation) {
841 // since the row group has a style bsize, cache the row bsizes,
842 // so next in flows can honor them
843 CacheRowBSizesForPrinting(GetFirstRow(), wm);
846 DidResizeRows(aDesiredSize);
848 aDesiredSize.BSize(wm) = rowGroupBSize; // Adjust our desired size
851 nscoord nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aBTotalOffset,
852 nscoord aISize,
853 WritingMode aWM) {
854 nsTableFrame* tableFrame = GetTableFrame();
855 nsSize containerSize = tableFrame->GetSize();
856 const nsStyleVisibility* groupVis = StyleVisibility();
857 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
858 if (collapseGroup) {
859 tableFrame->SetNeedToCollapse(true);
862 OverflowAreas overflow;
864 nsTableRowFrame* rowFrame = GetFirstRow();
865 bool didCollapse = false;
866 nscoord bGroupOffset = 0;
867 while (rowFrame) {
868 bGroupOffset += rowFrame->CollapseRowIfNecessary(
869 bGroupOffset, aISize, collapseGroup, didCollapse);
870 ConsiderChildOverflow(overflow, rowFrame);
871 rowFrame = rowFrame->GetNextRow();
874 LogicalRect groupRect = GetLogicalRect(aWM, containerSize);
875 nsRect oldGroupRect = GetRect();
876 nsRect oldGroupInkOverflow = InkOverflowRect();
878 groupRect.BSize(aWM) -= bGroupOffset;
879 if (didCollapse) {
880 // add back the cellspacing between rowgroups
881 groupRect.BSize(aWM) +=
882 tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount());
885 groupRect.BStart(aWM) -= aBTotalOffset;
886 groupRect.ISize(aWM) = aISize;
888 if (aBTotalOffset != 0) {
889 InvalidateFrameSubtree();
892 SetRect(aWM, groupRect, containerSize);
893 overflow.UnionAllWith(
894 nsRect(0, 0, groupRect.Width(aWM), groupRect.Height(aWM)));
895 FinishAndStoreOverflow(overflow, groupRect.Size(aWM).GetPhysicalSize(aWM));
896 nsTableFrame::RePositionViews(this);
897 nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupInkOverflow,
898 false);
900 return bGroupOffset;
903 nsTableRowFrame* nsTableRowGroupFrame::CreateContinuingRowFrame(
904 nsIFrame* aRowFrame) {
905 // Create the continuing frame which will create continuing cell frames.
906 auto* contRowFrame = static_cast<nsTableRowFrame*>(
907 PresShell()->FrameConstructor()->CreateContinuingFrame(aRowFrame, this));
909 // Add the continuing row frame to the child list.
910 mFrames.InsertFrame(nullptr, aRowFrame, contRowFrame);
912 // Push the continuing row frame and the frames that follow.
913 // This needs to match `UndoContinuedRow`.
914 PushChildrenToOverflow(contRowFrame, aRowFrame);
916 return contRowFrame;
919 // Reflow the cells with rowspan > 1 which originate between aFirstRow
920 // and end on or after aLastRow. aFirstTruncatedRow is the highest row on the
921 // page that contains a cell which cannot split on this page
922 void nsTableRowGroupFrame::SplitSpanningCells(
923 nsPresContext* aPresContext, const ReflowInput& aReflowInput,
924 nsTableFrame* aTable, nsTableRowFrame* aFirstRow, nsTableRowFrame* aLastRow,
925 bool aFirstRowIsTopOfPage, nscoord aSpanningRowBEnd,
926 const nsSize& aContainerSize, nsTableRowFrame*& aContRow,
927 nsTableRowFrame*& aFirstTruncatedRow, nscoord& aDesiredBSize) {
928 NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes");
929 aFirstTruncatedRow = nullptr;
930 aDesiredBSize = 0;
932 const WritingMode wm = aReflowInput.GetWritingMode();
933 const bool borderCollapse = aTable->IsBorderCollapse();
934 int32_t lastRowIndex = aLastRow->GetRowIndex();
935 bool wasLast = false;
936 bool haveRowSpan = false;
937 // Iterate the rows between aFirstRow and aLastRow
938 for (nsTableRowFrame* row = aFirstRow; !wasLast; row = row->GetNextRow()) {
939 wasLast = (row == aLastRow);
940 int32_t rowIndex = row->GetRowIndex();
941 const LogicalRect rowRect = row->GetLogicalNormalRect(wm, aContainerSize);
942 // Iterate the cells looking for those that have rowspan > 1
943 for (nsTableCellFrame* cell = row->GetFirstCell(); cell;
944 cell = cell->GetNextCell()) {
945 int32_t rowSpan = aTable->GetEffectiveRowSpan(rowIndex, *cell);
946 // Only reflow rowspan > 1 cells which span aLastRow. Those which don't
947 // span aLastRow were reflowed correctly during the unconstrained bsize
948 // reflow.
949 if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) {
950 haveRowSpan = true;
951 nsReflowStatus status;
952 // Ask the row to reflow the cell to the bsize of all the rows it spans
953 // up through aLastRow cellAvailBSize is the space between the row group
954 // start and the end of the page
955 const nscoord cellAvailBSize = aSpanningRowBEnd - rowRect.BStart(wm);
956 NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?");
957 bool isTopOfPage = (row == aFirstRow) && aFirstRowIsTopOfPage;
959 LogicalSize rowAvailSize(
960 wm, aReflowInput.AvailableISize(),
961 std::max(aReflowInput.AvailableBSize() - rowRect.BStart(wm), 0));
962 // Don't let the available block-size exceed what CalculateRowBSizes set
963 // for it.
964 rowAvailSize.BSize(wm) =
965 std::min(rowAvailSize.BSize(wm), rowRect.BSize(wm));
966 ReflowInput rowReflowInput(
967 aPresContext, aReflowInput, row,
968 rowAvailSize.ConvertTo(row->GetWritingMode(), wm), Nothing(),
969 ReflowInput::InitFlag::CallerWillInit);
970 InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput);
971 rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page
973 nscoord cellBSize =
974 row->ReflowCellFrame(aPresContext, rowReflowInput, isTopOfPage,
975 cell, cellAvailBSize, status);
976 aDesiredBSize = std::max(aDesiredBSize, rowRect.BStart(wm) + cellBSize);
977 if (status.IsComplete()) {
978 if (cellBSize > cellAvailBSize) {
979 aFirstTruncatedRow = row;
980 if ((row != aFirstRow) || !aFirstRowIsTopOfPage) {
981 // return now, since we will be getting another reflow after
982 // either (1) row is moved to the next page or (2) the row group
983 // is moved to the next page
984 return;
987 } else {
988 if (!aContRow) {
989 aContRow = CreateContinuingRowFrame(aLastRow);
991 if (aContRow) {
992 if (row != aLastRow) {
993 // aContRow needs a continuation for cell, since cell spanned into
994 // aLastRow but does not originate there
995 nsTableCellFrame* contCell = static_cast<nsTableCellFrame*>(
996 PresShell()->FrameConstructor()->CreateContinuingFrame(
997 cell, aLastRow));
998 uint32_t colIndex = cell->ColIndex();
999 aContRow->InsertCellFrame(contCell, colIndex);
1006 if (!haveRowSpan) {
1007 aDesiredBSize = aLastRow->GetLogicalNormalRect(wm, aContainerSize).BEnd(wm);
1011 // Remove the next-in-flow of the row, its cells and their cell blocks. This
1012 // is necessary in case the row doesn't need a continuation later on or needs
1013 // a continuation which doesn't have the same number of cells that now exist.
1014 void nsTableRowGroupFrame::UndoContinuedRow(nsPresContext* aPresContext,
1015 nsTableRowFrame* aRow) {
1016 if (!aRow) return; // allow null aRow to avoid callers doing null checks
1018 // rowBefore was the prev-sibling of aRow's next-sibling before aRow was
1019 // created
1020 nsTableRowFrame* rowBefore = (nsTableRowFrame*)aRow->GetPrevInFlow();
1021 MOZ_ASSERT(mFrames.ContainsFrame(rowBefore),
1022 "rowBefore not in our frame list?");
1024 // Needs to match `CreateContinuingRowFrame` - we're assuming that continued
1025 // frames always go into overflow frames list.
1026 AutoFrameListPtr overflows(aPresContext, StealOverflowFrames());
1027 if (!rowBefore || !overflows || overflows->IsEmpty() ||
1028 overflows->FirstChild() != aRow) {
1029 NS_ERROR("invalid continued row");
1030 return;
1033 DestroyContext context(aPresContext->PresShell());
1034 // Destroy aRow, its cells, and their cell blocks. Cell blocks that have split
1035 // will not have reflowed yet to pick up content from any overflow lines.
1036 overflows->DestroyFrame(context, aRow);
1038 // Put the overflow rows into our child list
1039 if (!overflows->IsEmpty()) {
1040 mFrames.InsertFrames(nullptr, rowBefore, std::move(*overflows));
1044 void nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext,
1045 ReflowOutput& aDesiredSize,
1046 const ReflowInput& aReflowInput,
1047 nsTableFrame* aTableFrame,
1048 nsReflowStatus& aStatus,
1049 bool aRowForcedPageBreak) {
1050 MOZ_ASSERT(aPresContext->IsPaginated(),
1051 "SplitRowGroup currently supports only paged media");
1053 const WritingMode wm = aReflowInput.GetWritingMode();
1054 nsTableRowFrame* prevRowFrame = nullptr;
1055 aDesiredSize.BSize(wm) = 0;
1056 aDesiredSize.SetOverflowAreasToDesiredBounds();
1058 const nscoord availISize = aReflowInput.AvailableISize();
1059 const nscoord availBSize = aReflowInput.AvailableBSize();
1060 const nsSize containerSize =
1061 aReflowInput.ComputedSizeAsContainerIfConstrained();
1062 const bool borderCollapse = aTableFrame->IsBorderCollapse();
1064 const nscoord pageBSize =
1065 LogicalSize(wm, aPresContext->GetPageSize()).BSize(wm);
1066 NS_ASSERTION(pageBSize != NS_UNCONSTRAINEDSIZE,
1067 "The table shouldn't be split when there should be space");
1069 bool isTopOfPage = aReflowInput.mFlags.mIsTopOfPage;
1070 nsTableRowFrame* firstRowThisPage = GetFirstRow();
1072 // Need to dirty the table's geometry, or else the row might skip
1073 // reflowing its cell as an optimization.
1074 aTableFrame->SetGeometryDirty();
1076 // Walk each of the row frames looking for the first row frame that doesn't
1077 // fit in the available space
1078 for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame;
1079 rowFrame = rowFrame->GetNextRow()) {
1080 bool rowIsOnPage = true;
1081 const nscoord rowSpacing =
1082 aTableFrame->GetRowSpacing(rowFrame->GetRowIndex());
1083 const LogicalRect rowRect =
1084 rowFrame->GetLogicalNormalRect(wm, containerSize);
1085 // See if the row fits on this page
1086 if (rowRect.BEnd(wm) > availBSize) {
1087 nsTableRowFrame* contRow = nullptr;
1088 // Reflow the row in the availabe space and have it split if it is the 1st
1089 // row (on the page) or there is at least 5% of the current page available
1090 // XXX this 5% should be made a preference
1091 if (!prevRowFrame ||
1092 (availBSize - aDesiredSize.BSize(wm) > pageBSize / 20)) {
1093 LogicalSize availSize(wm, availISize,
1094 std::max(availBSize - rowRect.BStart(wm), 0));
1095 // Don't let the available block-size exceed what CalculateRowBSizes set
1096 // for it.
1097 availSize.BSize(wm) = std::min(availSize.BSize(wm), rowRect.BSize(wm));
1099 ReflowInput rowReflowInput(
1100 aPresContext, aReflowInput, rowFrame,
1101 availSize.ConvertTo(rowFrame->GetWritingMode(), wm), Nothing(),
1102 ReflowInput::InitFlag::CallerWillInit);
1104 InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput);
1105 rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page
1106 ReflowOutput rowMetrics(aReflowInput);
1108 // Get the old size before we reflow.
1109 nsRect oldRowRect = rowFrame->GetRect();
1110 nsRect oldRowInkOverflow = rowFrame->InkOverflowRect();
1112 // Reflow the cell with the constrained bsize. A cell with rowspan >1
1113 // will get this reflow later during SplitSpanningCells.
1115 // Note: We just pass dummy aPos and aContainerSize since we are not
1116 // moving the row frame.
1117 const LogicalPoint dummyPos(wm);
1118 const nsSize dummyContainerSize;
1119 ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput, wm,
1120 dummyPos, dummyContainerSize, ReflowChildFlags::NoMoveFrame,
1121 aStatus);
1122 FinishReflowChild(rowFrame, aPresContext, rowMetrics, &rowReflowInput,
1123 wm, dummyPos, dummyContainerSize,
1124 ReflowChildFlags::NoMoveFrame);
1125 rowFrame->DidResize(ForceAlignTopForTableCell::Yes);
1127 if (!aRowForcedPageBreak && !aStatus.IsFullyComplete() &&
1128 ShouldAvoidBreakInside(aReflowInput)) {
1129 aStatus.SetInlineLineBreakBeforeAndReset();
1130 break;
1133 nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect,
1134 oldRowInkOverflow, false);
1136 if (aStatus.IsIncomplete()) {
1137 // The row frame is incomplete and all of the rowspan 1 cells' block
1138 // frames split
1139 if ((rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize()) ||
1140 isTopOfPage) {
1141 // The row stays on this page because either it split ok or we're on
1142 // the top of page. If top of page and the block-size exceeded the
1143 // avail block-size, then there will be data loss.
1144 NS_ASSERTION(
1145 rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize(),
1146 "Data loss - incomplete row needed more block-size than "
1147 "available, on top of page!");
1148 contRow = CreateContinuingRowFrame(rowFrame);
1149 aDesiredSize.BSize(wm) += rowMetrics.BSize(wm);
1150 if (prevRowFrame) {
1151 aDesiredSize.BSize(wm) += rowSpacing;
1153 } else {
1154 // Put the row on the next page to give it more block-size.
1155 rowIsOnPage = false;
1157 } else {
1158 // The row frame is complete because either (1) its minimum block-size
1159 // is greater than the available block-size we gave it, or (2) it may
1160 // have been given a larger block-size through style than its content,
1161 // or (3) it contains a rowspan >1 cell which hasn't been reflowed
1162 // with a constrained block-size yet (we will find out when
1163 // SplitSpanningCells is called below)
1164 if (rowMetrics.BSize(wm) > availSize.BSize(wm) ||
1165 (aStatus.IsInlineBreakBefore() && !aRowForcedPageBreak)) {
1166 // cases (1) and (2)
1167 if (isTopOfPage) {
1168 // We're on top of the page, so keep the row on this page. There
1169 // will be data loss. Push the row frame that follows
1170 nsTableRowFrame* nextRowFrame = rowFrame->GetNextRow();
1171 if (nextRowFrame) {
1172 aStatus.Reset();
1173 aStatus.SetIncomplete();
1175 aDesiredSize.BSize(wm) += rowMetrics.BSize(wm);
1176 if (prevRowFrame) {
1177 aDesiredSize.BSize(wm) += rowSpacing;
1179 NS_WARNING(
1180 "Data loss - complete row needed more block-size than "
1181 "available, on top of page");
1182 } else {
1183 // We're not on top of the page, so put the row on the next page
1184 // to give it more block-size.
1185 rowIsOnPage = false;
1189 } else {
1190 // Put the row on the next page to give it more block-size.
1191 rowIsOnPage = false;
1194 nsTableRowFrame* lastRowThisPage = rowFrame;
1195 nscoord spanningRowBEnd = availBSize;
1196 if (!rowIsOnPage) {
1197 NS_ASSERTION(!contRow,
1198 "We should not have created a continuation if none of "
1199 "this row fits");
1200 if (!prevRowFrame ||
1201 (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput))) {
1202 aStatus.SetInlineLineBreakBeforeAndReset();
1203 break;
1205 spanningRowBEnd =
1206 prevRowFrame->GetLogicalNormalRect(wm, containerSize).BEnd(wm);
1207 lastRowThisPage = prevRowFrame;
1208 aStatus.Reset();
1209 aStatus.SetIncomplete();
1212 // reflow the cells with rowspan >1 that occur on the page
1213 nsTableRowFrame* firstTruncatedRow;
1214 nscoord bMost;
1215 SplitSpanningCells(aPresContext, aReflowInput, aTableFrame,
1216 firstRowThisPage, lastRowThisPage,
1217 aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd,
1218 containerSize, contRow, firstTruncatedRow, bMost);
1219 if (firstTruncatedRow) {
1220 // A rowspan >1 cell did not fit (and could not split) in the space we
1221 // gave it
1222 if (firstTruncatedRow == firstRowThisPage) {
1223 if (aReflowInput.mFlags.mIsTopOfPage) {
1224 NS_WARNING("data loss in a row spanned cell");
1225 } else {
1226 // We can't push children, so let our parent reflow us again with
1227 // more space
1228 aDesiredSize.BSize(wm) = rowRect.BEnd(wm);
1229 aStatus.Reset();
1230 UndoContinuedRow(aPresContext, contRow);
1231 contRow = nullptr;
1233 } else {
1234 // Try to put firstTruncateRow on the next page
1235 nsTableRowFrame* rowBefore = firstTruncatedRow->GetPrevRow();
1236 const nscoord oldSpanningRowBEnd = spanningRowBEnd;
1237 spanningRowBEnd =
1238 rowBefore->GetLogicalNormalRect(wm, containerSize).BEnd(wm);
1240 UndoContinuedRow(aPresContext, contRow);
1241 contRow = nullptr;
1242 nsTableRowFrame* oldLastRowThisPage = lastRowThisPage;
1243 lastRowThisPage = rowBefore;
1244 aStatus.Reset();
1245 aStatus.SetIncomplete();
1247 // Call SplitSpanningCells again with rowBefore as the last row on the
1248 // page
1249 SplitSpanningCells(aPresContext, aReflowInput, aTableFrame,
1250 firstRowThisPage, rowBefore,
1251 aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd,
1252 containerSize, contRow, firstTruncatedRow,
1253 aDesiredSize.BSize(wm));
1254 if (firstTruncatedRow) {
1255 if (aReflowInput.mFlags.mIsTopOfPage) {
1256 // We were better off with the 1st call to SplitSpanningCells, do
1257 // it again
1258 UndoContinuedRow(aPresContext, contRow);
1259 contRow = nullptr;
1260 lastRowThisPage = oldLastRowThisPage;
1261 spanningRowBEnd = oldSpanningRowBEnd;
1262 SplitSpanningCells(aPresContext, aReflowInput, aTableFrame,
1263 firstRowThisPage, lastRowThisPage,
1264 aReflowInput.mFlags.mIsTopOfPage,
1265 spanningRowBEnd, containerSize, contRow,
1266 firstTruncatedRow, aDesiredSize.BSize(wm));
1267 NS_WARNING("data loss in a row spanned cell");
1268 } else {
1269 // Let our parent reflow us again with more space
1270 aDesiredSize.BSize(wm) = rowRect.BEnd(wm);
1271 aStatus.Reset();
1272 UndoContinuedRow(aPresContext, contRow);
1273 contRow = nullptr;
1277 } else {
1278 aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bMost);
1279 if (contRow) {
1280 aStatus.Reset();
1281 aStatus.SetIncomplete();
1284 if (aStatus.IsIncomplete() && !contRow) {
1285 if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) {
1286 PushChildrenToOverflow(nextRow, lastRowThisPage);
1288 } else if (aStatus.IsComplete() && lastRowThisPage) {
1289 // Our size from the unconstrained reflow exceeded the constrained
1290 // available space but our size in the constrained reflow is Complete.
1291 // This can happen when a non-zero block-end margin is suppressed in
1292 // nsBlockFrame::ComputeFinalSize.
1293 if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) {
1294 aStatus.Reset();
1295 aStatus.SetIncomplete();
1296 PushChildrenToOverflow(nextRow, lastRowThisPage);
1299 break;
1301 aDesiredSize.BSize(wm) = rowRect.BEnd(wm);
1302 prevRowFrame = rowFrame;
1303 // see if there is a page break after the row
1304 nsTableRowFrame* nextRow = rowFrame->GetNextRow();
1305 if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) {
1306 PushChildrenToOverflow(nextRow, rowFrame);
1307 aStatus.Reset();
1308 aStatus.SetIncomplete();
1309 break;
1311 // After the 1st row that has a block-size, we can't be on top of the page
1312 // anymore.
1313 isTopOfPage = isTopOfPage && rowRect.BEnd(wm) == 0;
1317 /** Layout the entire row group.
1318 * This method stacks rows vertically according to HTML 4.0 rules.
1319 * Rows are responsible for layout of their children.
1321 void nsTableRowGroupFrame::Reflow(nsPresContext* aPresContext,
1322 ReflowOutput& aDesiredSize,
1323 const ReflowInput& aReflowInput,
1324 nsReflowStatus& aStatus) {
1325 MarkInReflow();
1326 DO_GLOBAL_REFLOW_COUNT("nsTableRowGroupFrame");
1327 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1329 // Row geometry may be going to change so we need to invalidate any row
1330 // cursor.
1331 ClearRowCursor();
1333 // see if a special bsize reflow needs to occur due to having a pct bsize
1334 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
1336 nsTableFrame* tableFrame = GetTableFrame();
1337 TableRowGroupReflowInput state(aReflowInput);
1338 const nsStyleVisibility* groupVis = StyleVisibility();
1339 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
1340 if (collapseGroup) {
1341 tableFrame->SetNeedToCollapse(true);
1344 // Check for an overflow list
1345 MoveOverflowToChildList();
1347 // Reflow the existing frames.
1348 bool splitDueToPageBreak = false;
1349 ReflowChildren(aPresContext, aDesiredSize, state, aStatus,
1350 &splitDueToPageBreak);
1352 // See if all the frames fit. Do not try to split anything if we're
1353 // not paginated ... we can't split across columns yet.
1354 WritingMode wm = aReflowInput.GetWritingMode();
1355 if (aReflowInput.mFlags.mTableIsSplittable &&
1356 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1357 (aStatus.IsIncomplete() || splitDueToPageBreak ||
1358 aDesiredSize.BSize(wm) > aReflowInput.AvailableBSize())) {
1359 // Nope, find a place to split the row group
1360 auto& mutableRIFlags = const_cast<ReflowInput::Flags&>(aReflowInput.mFlags);
1361 const bool savedSpecialBSizeReflow = mutableRIFlags.mSpecialBSizeReflow;
1362 mutableRIFlags.mSpecialBSizeReflow = false;
1364 SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus,
1365 splitDueToPageBreak);
1367 mutableRIFlags.mSpecialBSizeReflow = savedSpecialBSizeReflow;
1370 // XXXmats The following is just bogus. We leave it here for now because
1371 // ReflowChildren should pull up rows from our next-in-flow before returning
1372 // a Complete status, but doesn't (bug 804888).
1373 if (GetNextInFlow() && GetNextInFlow()->PrincipalChildList().FirstChild()) {
1374 aStatus.SetIncomplete();
1377 SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) &&
1378 (aReflowInput.ComputedBSize() > 0));
1380 // Just set our isize to what was available.
1381 // The table will calculate the isize and not use our value.
1382 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
1384 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
1386 // If our parent is in initial reflow, it'll handle invalidating our
1387 // entire overflow rect.
1388 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
1389 aDesiredSize.Size(wm) != GetLogicalSize(wm)) {
1390 InvalidateFrame();
1393 FinishAndStoreOverflow(&aDesiredSize);
1395 // Any absolutely-positioned children will get reflowed in
1396 // nsIFrame::FixupPositionedTableParts in another pass, so propagate our
1397 // dirtiness to them before our parent clears our dirty bits.
1398 PushDirtyBitToAbsoluteFrames();
1401 bool nsTableRowGroupFrame::ComputeCustomOverflow(
1402 OverflowAreas& aOverflowAreas) {
1403 // Row cursor invariants depend on the ink overflow area of the rows,
1404 // which may have changed, so we need to clear the cursor now.
1405 ClearRowCursor();
1406 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
1409 /* virtual */
1410 void nsTableRowGroupFrame::DidSetComputedStyle(
1411 ComputedStyle* aOldComputedStyle) {
1412 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
1413 nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle);
1415 if (!aOldComputedStyle) {
1416 return; // avoid the following on init
1419 nsTableFrame* tableFrame = GetTableFrame();
1420 if (tableFrame->IsBorderCollapse() &&
1421 tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) {
1422 TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(),
1423 GetRowCount());
1424 tableFrame->AddBCDamageArea(damageArea);
1428 void nsTableRowGroupFrame::AppendFrames(ChildListID aListID,
1429 nsFrameList&& aFrameList) {
1430 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1432 DrainSelfOverflowList(); // ensure the last frame is in mFrames
1433 ClearRowCursor();
1435 // collect the new row frames in an array
1436 // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
1437 AutoTArray<nsTableRowFrame*, 8> rows;
1438 for (nsIFrame* f : aFrameList) {
1439 nsTableRowFrame* rowFrame = do_QueryFrame(f);
1440 NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
1441 if (rowFrame) {
1442 NS_ASSERTION(
1443 mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay,
1444 "wrong display type on rowframe");
1445 rows.AppendElement(rowFrame);
1449 int32_t rowIndex = GetRowCount();
1450 // Append the frames to the sibling chain
1451 mFrames.AppendFrames(nullptr, std::move(aFrameList));
1453 if (rows.Length() > 0) {
1454 nsTableFrame* tableFrame = GetTableFrame();
1455 tableFrame->AppendRows(this, rowIndex, rows);
1456 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1457 NS_FRAME_HAS_DIRTY_CHILDREN);
1458 tableFrame->SetGeometryDirty();
1462 void nsTableRowGroupFrame::InsertFrames(
1463 ChildListID aListID, nsIFrame* aPrevFrame,
1464 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
1465 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1466 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
1467 "inserting after sibling frame with different parent");
1469 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
1470 ClearRowCursor();
1472 // collect the new row frames in an array
1473 // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
1474 nsTableFrame* tableFrame = GetTableFrame();
1475 nsTArray<nsTableRowFrame*> rows;
1476 bool gotFirstRow = false;
1477 for (nsIFrame* f : aFrameList) {
1478 nsTableRowFrame* rowFrame = do_QueryFrame(f);
1479 NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
1480 if (rowFrame) {
1481 NS_ASSERTION(
1482 mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay,
1483 "wrong display type on rowframe");
1484 rows.AppendElement(rowFrame);
1485 if (!gotFirstRow) {
1486 rowFrame->SetFirstInserted(true);
1487 gotFirstRow = true;
1488 tableFrame->SetRowInserted(true);
1493 int32_t startRowIndex = GetStartRowIndex();
1494 // Insert the frames in the sibling chain
1495 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
1497 int32_t numRows = rows.Length();
1498 if (numRows > 0) {
1499 nsTableRowFrame* prevRow =
1500 (nsTableRowFrame*)nsTableFrame::GetFrameAtOrBefore(
1501 this, aPrevFrame, LayoutFrameType::TableRow);
1502 int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex;
1503 tableFrame->InsertRows(this, rows, rowIndex, true);
1505 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1506 NS_FRAME_HAS_DIRTY_CHILDREN);
1507 tableFrame->SetGeometryDirty();
1511 void nsTableRowGroupFrame::RemoveFrame(DestroyContext& aContext,
1512 ChildListID aListID,
1513 nsIFrame* aOldFrame) {
1514 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1516 ClearRowCursor();
1518 // XXX why are we doing the QI stuff? There shouldn't be any non-rows here.
1519 nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame);
1520 if (rowFrame) {
1521 nsTableFrame* tableFrame = GetTableFrame();
1522 // remove the rows from the table (and flag a rebalance)
1523 tableFrame->RemoveRows(*rowFrame, 1, true);
1525 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1526 NS_FRAME_HAS_DIRTY_CHILDREN);
1527 tableFrame->SetGeometryDirty();
1529 mFrames.DestroyFrame(aContext, aOldFrame);
1532 /* virtual */
1533 nsMargin nsTableRowGroupFrame::GetUsedMargin() const {
1534 return nsMargin(0, 0, 0, 0);
1537 /* virtual */
1538 nsMargin nsTableRowGroupFrame::GetUsedBorder() const {
1539 return nsMargin(0, 0, 0, 0);
1542 /* virtual */
1543 nsMargin nsTableRowGroupFrame::GetUsedPadding() const {
1544 return nsMargin(0, 0, 0, 0);
1547 nscoord nsTableRowGroupFrame::GetBSizeBasis(const ReflowInput& aReflowInput) {
1548 nscoord result = 0;
1549 nsTableFrame* tableFrame = GetTableFrame();
1550 int32_t startRowIndex = GetStartRowIndex();
1551 if ((aReflowInput.ComputedBSize() > 0) &&
1552 (aReflowInput.ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
1553 nscoord cellSpacing = tableFrame->GetRowSpacing(
1554 startRowIndex,
1555 std::max(startRowIndex, startRowIndex + GetRowCount() - 1));
1556 result = aReflowInput.ComputedBSize() - cellSpacing;
1557 } else {
1558 const ReflowInput* parentRI = aReflowInput.mParentReflowInput;
1559 if (parentRI && (tableFrame != parentRI->mFrame)) {
1560 parentRI = parentRI->mParentReflowInput;
1562 if (parentRI && (tableFrame == parentRI->mFrame) &&
1563 (parentRI->ComputedBSize() > 0) &&
1564 (parentRI->ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
1565 nscoord cellSpacing =
1566 tableFrame->GetRowSpacing(-1, tableFrame->GetRowCount());
1567 result = parentRI->ComputedBSize() - cellSpacing;
1571 return result;
1574 bool nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame,
1575 nsTableRowFrame* aRowFrame) {
1576 int32_t rowIndex = aRowFrame->GetRowIndex();
1578 // It's a simple row frame if there are no cells that span into or
1579 // across the row
1580 int32_t numEffCols = aTableFrame->GetEffectiveColCount();
1581 if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) &&
1582 !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) {
1583 return true;
1586 return false;
1589 /** find page break before the first row **/
1590 bool nsTableRowGroupFrame::HasInternalBreakBefore() const {
1591 nsIFrame* firstChild = mFrames.FirstChild();
1592 if (!firstChild) return false;
1593 return firstChild->StyleDisplay()->BreakBefore();
1596 /** find page break after the last row **/
1597 bool nsTableRowGroupFrame::HasInternalBreakAfter() const {
1598 nsIFrame* lastChild = mFrames.LastChild();
1599 if (!lastChild) return false;
1600 return lastChild->StyleDisplay()->BreakAfter();
1602 /* ----- global methods ----- */
1604 nsTableRowGroupFrame* NS_NewTableRowGroupFrame(PresShell* aPresShell,
1605 ComputedStyle* aStyle) {
1606 return new (aPresShell)
1607 nsTableRowGroupFrame(aStyle, aPresShell->GetPresContext());
1610 NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame)
1612 #ifdef DEBUG_FRAME_DUMP
1613 nsresult nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const {
1614 return MakeFrameName(u"TableRowGroup"_ns, aResult);
1616 #endif
1618 LogicalMargin nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) {
1619 LogicalMargin border(aWM);
1620 nsTableRowFrame* firstRowFrame = GetFirstRow();
1621 if (!firstRowFrame) {
1622 return border;
1624 nsTableRowFrame* lastRowFrame = firstRowFrame;
1625 for (nsTableRowFrame* rowFrame = firstRowFrame->GetNextRow(); rowFrame;
1626 rowFrame = rowFrame->GetNextRow()) {
1627 lastRowFrame = rowFrame;
1629 border.BStart(aWM) = PresContext()->DevPixelsToAppUnits(
1630 firstRowFrame->GetBStartBCBorderWidth());
1631 border.BEnd(aWM) =
1632 PresContext()->DevPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth());
1633 return border;
1636 // nsILineIterator methods
1637 int32_t nsTableRowGroupFrame::GetNumLines() const { return GetRowCount(); }
1639 bool nsTableRowGroupFrame::IsLineIteratorFlowRTL() {
1640 return StyleDirection::Rtl == GetTableFrame()->StyleVisibility()->mDirection;
1643 Result<nsILineIterator::LineInfo, nsresult> nsTableRowGroupFrame::GetLine(
1644 int32_t aLineNumber) {
1645 if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) {
1646 return Err(NS_ERROR_FAILURE);
1648 LineInfo structure;
1649 nsTableFrame* table = GetTableFrame();
1650 nsTableCellMap* cellMap = table->GetCellMap();
1651 aLineNumber += GetStartRowIndex();
1653 structure.mNumFramesOnLine =
1654 cellMap->GetNumCellsOriginatingInRow(aLineNumber);
1655 if (structure.mNumFramesOnLine == 0) {
1656 return structure;
1658 int32_t colCount = table->GetColCount();
1659 for (int32_t i = 0; i < colCount; i++) {
1660 CellData* data = cellMap->GetDataAt(aLineNumber, i);
1661 if (data && data->IsOrig()) {
1662 structure.mFirstFrameOnLine = (nsIFrame*)data->GetCellFrame();
1663 nsIFrame* parent = structure.mFirstFrameOnLine->GetParent();
1664 structure.mLineBounds = parent->GetRect();
1665 return structure;
1668 MOZ_ASSERT_UNREACHABLE("cellmap is lying");
1669 return Err(NS_ERROR_FAILURE);
1672 int32_t nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame,
1673 int32_t aStartLine) {
1674 NS_ENSURE_TRUE(aFrame, -1);
1676 nsTableRowFrame* rowFrame = do_QueryFrame(aFrame);
1677 if (MOZ_UNLIKELY(!rowFrame)) {
1678 // When we do not have valid table structure in the DOM tree, somebody wants
1679 // to check the line number with an out-of-flow child of this frame because
1680 // its parent frame is set to this frame. Otherwise, the caller must have
1681 // a bug.
1682 MOZ_ASSERT(aFrame->GetParent() == this);
1683 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
1684 return -1;
1687 int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex();
1689 return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1;
1692 NS_IMETHODIMP
1693 nsTableRowGroupFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
1694 nsIFrame** aFirstVisual,
1695 nsIFrame** aLastVisual) {
1696 *aIsReordered = false;
1697 *aFirstVisual = nullptr;
1698 *aLastVisual = nullptr;
1699 return NS_OK;
1702 NS_IMETHODIMP
1703 nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
1704 nsIFrame** aFrameFound,
1705 bool* aPosIsBeforeFirstFrame,
1706 bool* aPosIsAfterLastFrame) {
1707 nsTableFrame* table = GetTableFrame();
1708 nsTableCellMap* cellMap = table->GetCellMap();
1710 *aFrameFound = nullptr;
1711 *aPosIsBeforeFirstFrame = true;
1712 *aPosIsAfterLastFrame = false;
1714 aLineNumber += GetStartRowIndex();
1715 int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber);
1716 if (numCells == 0) {
1717 return NS_OK;
1720 nsIFrame* frame = nullptr;
1721 int32_t colCount = table->GetColCount();
1722 for (int32_t i = 0; i < colCount; i++) {
1723 CellData* data = cellMap->GetDataAt(aLineNumber, i);
1724 if (data && data->IsOrig()) {
1725 frame = (nsIFrame*)data->GetCellFrame();
1726 break;
1729 NS_ASSERTION(frame, "cellmap is lying");
1730 bool isRTL = StyleDirection::Rtl == table->StyleVisibility()->mDirection;
1732 LineFrameFinder finder(aPos, table->GetSize(), table->GetWritingMode(),
1733 isRTL);
1735 int32_t n = numCells;
1736 while (n--) {
1737 finder.Scan(frame);
1738 if (finder.IsDone()) {
1739 break;
1741 frame = frame->GetNextSibling();
1743 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
1744 return NS_OK;
1747 // end nsLineIterator methods
1749 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty,
1750 nsTableRowGroupFrame::FrameCursorData)
1752 void nsTableRowGroupFrame::ClearRowCursor() {
1753 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1754 return;
1757 RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
1758 RemoveProperty(RowCursorProperty());
1761 nsTableRowGroupFrame::FrameCursorData* nsTableRowGroupFrame::SetupRowCursor() {
1762 if (HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1763 // We already have a valid row cursor. Don't waste time rebuilding it.
1764 return nullptr;
1767 nsIFrame* f = mFrames.FirstChild();
1768 int32_t count;
1769 for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) {
1770 f = f->GetNextSibling();
1772 if (!f) {
1773 // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother
1774 return nullptr;
1777 FrameCursorData* data = new FrameCursorData();
1778 SetProperty(RowCursorProperty(), data);
1779 AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
1780 return data;
1783 nsIFrame* nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY,
1784 nscoord* aOverflowAbove) {
1785 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1786 return nullptr;
1789 FrameCursorData* property = GetProperty(RowCursorProperty());
1790 uint32_t cursorIndex = property->mCursorIndex;
1791 uint32_t frameCount = property->mFrames.Length();
1792 if (cursorIndex >= frameCount) return nullptr;
1793 nsIFrame* cursorFrame = property->mFrames[cursorIndex];
1795 // The cursor's frame list excludes frames with empty overflow-area, so
1796 // we don't need to check that here.
1798 // We use property->mOverflowBelow here instead of computing the frame's
1799 // true overflowArea.YMost(), because it is essential for the thresholds
1800 // to form a monotonically increasing sequence. Otherwise we would break
1801 // encountering a row whose overflowArea.YMost() is <= aY but which has
1802 // a row above it containing cell(s) that span to include aY.
1803 while (cursorIndex > 0 &&
1804 cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) {
1805 --cursorIndex;
1806 cursorFrame = property->mFrames[cursorIndex];
1808 while (cursorIndex + 1 < frameCount &&
1809 cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) {
1810 ++cursorIndex;
1811 cursorFrame = property->mFrames[cursorIndex];
1814 property->mCursorIndex = cursorIndex;
1815 *aOverflowAbove = property->mOverflowAbove;
1816 return cursorFrame;
1819 bool nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) {
1820 // The cursor requires a monotonically increasing sequence in order to
1821 // identify which rows can be skipped, and position:relative can move
1822 // rows around such that the overflow areas don't provide this.
1823 // We take the union of the overflow rect, and the frame's 'normal' position
1824 // (excluding position:relative changes) and record the max difference between
1825 // this combined overflow and the frame's rect.
1826 nsRect positionedOverflowRect = aFrame->InkOverflowRect();
1827 nsPoint positionedToNormal =
1828 aFrame->GetNormalPosition() - aFrame->GetPosition();
1829 nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal;
1831 nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect);
1832 if (overflowRect.IsEmpty()) return true;
1833 nscoord overflowAbove = -overflowRect.y;
1834 nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height;
1835 mOverflowAbove = std::max(mOverflowAbove, overflowAbove);
1836 mOverflowBelow = std::max(mOverflowBelow, overflowBelow);
1837 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1838 // pretended earlier, or change the return type to void.
1839 mFrames.AppendElement(aFrame);
1840 return true;
1843 void nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey,
1844 bool aRebuildDisplayItems) {
1845 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
1846 if (GetTableFrame()->IsBorderCollapse()) {
1847 const bool rebuild = StaticPrefs::layout_display_list_retain_sc();
1848 GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
1849 aDisplayItemKey, rebuild);
1853 void nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect,
1854 uint32_t aDisplayItemKey,
1855 bool aRebuildDisplayItems) {
1856 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
1857 aRebuildDisplayItems);
1858 // If we have filters applied that would affects our bounds, then
1859 // we get an inactive layer created and this is computed
1860 // within FrameLayerBuilder
1861 GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey,
1862 aRebuildDisplayItems);