Bug 1919083 - [ci] Enable os-integration variant for more suites, r=jmaher
[gecko.git] / layout / tables / nsTableRowGroupFrame.cpp
blob2a61e123f1633016c8046979b751afc6a252b2f1
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);
108 int32_t nsTableRowGroupFrame::GetAdjustmentForStoredIndex(
109 int32_t aStoredIndex) {
110 nsTableFrame* tableFrame = GetTableFrame();
111 return tableFrame->GetAdjustmentForStoredIndex(aStoredIndex);
114 void nsTableRowGroupFrame::MarkRowsAsDeleted(nsTableRowFrame& aStartRowFrame,
115 int32_t aNumRowsToDelete) {
116 nsTableRowFrame* currentRowFrame = &aStartRowFrame;
117 for (;;) {
118 // XXXneerja - Instead of calling AddDeletedRowIndex() per row frame
119 // it is possible to change AddDeleteRowIndex to instead take
120 // <start row index> and <num of rows to mark for deletion> as arguments.
121 // The problem that emerges here is mDeletedRowIndexRanges only stores
122 // disjoint index ranges and since AddDeletedRowIndex() must operate on
123 // the "stored" index, in some cases it is possible that the range
124 // of indices to delete becomes overlapping EG: Deleting rows 9 - 11 and
125 // then from the remaining rows deleting the *new* rows 7 to 20.
126 // Handling these overlapping ranges is much more complicated to
127 // implement and so I opted to add the deleted row index of one row at a
128 // time and maintain the invariant that the range of deleted row indices
129 // is always disjoint.
130 currentRowFrame->AddDeletedRowIndex();
131 if (--aNumRowsToDelete == 0) {
132 break;
134 currentRowFrame = do_QueryFrame(currentRowFrame->GetNextSibling());
135 if (!currentRowFrame) {
136 MOZ_ASSERT_UNREACHABLE("expected another row frame");
137 break;
142 void nsTableRowGroupFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) {
143 nsTableFrame* tableFrame = GetTableFrame();
144 return tableFrame->AddDeletedRowIndex(aDeletedRowStoredIndex);
147 void nsTableRowGroupFrame::InitRepeatedFrame(
148 nsTableRowGroupFrame* aHeaderFooterFrame) {
149 nsTableRowFrame* copyRowFrame = GetFirstRow();
150 nsTableRowFrame* originalRowFrame = aHeaderFooterFrame->GetFirstRow();
151 AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
152 while (copyRowFrame && originalRowFrame) {
153 copyRowFrame->AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
154 int rowIndex = originalRowFrame->GetRowIndex();
155 copyRowFrame->SetRowIndex(rowIndex);
157 // For each table cell frame set its column index
158 nsTableCellFrame* originalCellFrame = originalRowFrame->GetFirstCell();
159 nsTableCellFrame* copyCellFrame = copyRowFrame->GetFirstCell();
160 while (copyCellFrame && originalCellFrame) {
161 NS_ASSERTION(
162 originalCellFrame->GetContent() == copyCellFrame->GetContent(),
163 "cell frames have different content");
164 uint32_t colIndex = originalCellFrame->ColIndex();
165 copyCellFrame->SetColIndex(colIndex);
167 // Move to the next cell frame
168 copyCellFrame = copyCellFrame->GetNextCell();
169 originalCellFrame = originalCellFrame->GetNextCell();
172 // Move to the next row frame
173 originalRowFrame = originalRowFrame->GetNextRow();
174 copyRowFrame = copyRowFrame->GetNextRow();
178 // Handle the child-traversal part of DisplayGenericTablePart
179 static void DisplayRows(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
180 const nsDisplayListSet& aLists) {
181 nscoord overflowAbove;
182 nsTableRowGroupFrame* f = static_cast<nsTableRowGroupFrame*>(aFrame);
183 // Don't try to use the row cursor if we have to descend into placeholders;
184 // we might have rows containing placeholders, where the row's overflow
185 // area doesn't intersect the dirty rect but we need to descend into the row
186 // to see out of flows.
187 // Note that we really want to check ShouldDescendIntoFrame for all
188 // the rows in |f|, but that's exactly what we're trying to avoid, so we
189 // approximate it by checking it for |f|: if it's true for any row
190 // in |f| then it's true for |f| itself.
191 nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(f, true)
192 ? nullptr
193 : f->GetFirstRowContaining(aBuilder->GetVisibleRect().y,
194 &overflowAbove);
196 if (kid) {
197 // have a cursor, use it
198 while (kid) {
199 if (kid->GetRect().y - overflowAbove >=
200 aBuilder->GetVisibleRect().YMost()) {
201 break;
203 f->BuildDisplayListForChild(aBuilder, kid, aLists);
204 kid = kid->GetNextSibling();
206 return;
209 // No cursor. Traverse children the hard way and build a cursor while we're at
210 // it
211 nsTableRowGroupFrame::FrameCursorData* cursor = f->SetupRowCursor();
212 kid = f->PrincipalChildList().FirstChild();
213 while (kid) {
214 f->BuildDisplayListForChild(aBuilder, kid, aLists);
216 if (cursor) {
217 if (!cursor->AppendFrame(kid)) {
218 f->ClearRowCursor();
219 return;
223 kid = kid->GetNextSibling();
225 if (cursor) {
226 cursor->FinishBuildingCursor();
230 void nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
231 const nsDisplayListSet& aLists) {
232 DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground());
234 for (nsTableRowFrame* row = GetFirstRow(); row; row = row->GetNextRow()) {
235 if (!aBuilder->GetDirtyRect().Intersects(row->InkOverflowRect() +
236 row->GetNormalPosition())) {
237 continue;
239 row->PaintCellBackgroundsForFrame(this, aBuilder, aLists,
240 row->GetNormalPosition());
243 DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground());
245 DisplayOutline(aBuilder, aLists);
247 DisplayRows(aBuilder, this, aLists);
250 LogicalSides nsTableRowGroupFrame::GetLogicalSkipSides() const {
251 LogicalSides skip(mWritingMode);
252 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
253 StyleBoxDecorationBreak::Clone)) {
254 return skip;
257 if (GetPrevInFlow()) {
258 skip += LogicalSide::BStart;
260 if (GetNextInFlow()) {
261 skip += LogicalSide::BEnd;
263 return skip;
266 // Position and size aKidFrame and update our reflow input.
267 void nsTableRowGroupFrame::PlaceChild(
268 nsPresContext* aPresContext, TableRowGroupReflowInput& aReflowInput,
269 nsIFrame* aKidFrame, const ReflowInput& aKidReflowInput, WritingMode aWM,
270 const LogicalPoint& aKidPosition, const nsSize& aContainerSize,
271 ReflowOutput& aDesiredSize, const nsRect& aOriginalKidRect,
272 const nsRect& aOriginalKidInkOverflow) {
273 bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
275 // Place and size the child
276 FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, &aKidReflowInput,
277 aWM, aKidPosition, aContainerSize,
278 ReflowChildFlags::ApplyRelativePositioning);
280 nsTableFrame* tableFrame = GetTableFrame();
281 if (tableFrame->IsBorderCollapse()) {
282 nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect,
283 aOriginalKidInkOverflow, isFirstReflow);
286 // Adjust the running block-offset
287 aReflowInput.mBCoord += aDesiredSize.BSize(aWM);
289 // If our block-size is constrained then update the available bsize
290 if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(aWM)) {
291 aReflowInput.mAvailSize.BSize(aWM) -= aDesiredSize.BSize(aWM);
295 void nsTableRowGroupFrame::InitChildReflowInput(nsPresContext* aPresContext,
296 bool aBorderCollapse,
297 ReflowInput& aReflowInput) {
298 const auto childWM = aReflowInput.GetWritingMode();
299 LogicalMargin border(childWM);
300 if (aBorderCollapse) {
301 auto* rowFrame = static_cast<nsTableRowFrame*>(aReflowInput.mFrame);
302 border = rowFrame->GetBCBorderWidth(childWM);
304 const LogicalMargin zeroPadding(childWM);
305 aReflowInput.Init(aPresContext, Nothing(), Some(border), Some(zeroPadding));
308 static void CacheRowBSizesForPrinting(nsTableRowFrame* aFirstRow,
309 WritingMode aWM) {
310 for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) {
311 if (!row->GetPrevInFlow()) {
312 row->SetUnpaginatedBSize(row->BSize(aWM));
317 void nsTableRowGroupFrame::ReflowChildren(
318 nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
319 TableRowGroupReflowInput& aReflowInput, nsReflowStatus& aStatus,
320 bool* aPageBreakBeforeEnd) {
321 if (aPageBreakBeforeEnd) {
322 *aPageBreakBeforeEnd = false;
325 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode();
326 nsTableFrame* tableFrame = GetTableFrame();
327 const bool borderCollapse = tableFrame->IsBorderCollapse();
329 // XXXldb Should we really be checking IsPaginated(),
330 // or should we *only* check available block-size?
331 // (Think about multi-column layout!)
332 bool isPaginated = aPresContext->IsPaginated() &&
333 NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm);
335 bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() ||
336 tableFrame->IsGeometryDirty() ||
337 tableFrame->NeedToCollapse();
339 // in vertical-rl mode, we always need the row bsizes in order to
340 // get the necessary containerSize for placing our kids
341 bool needToCalcRowBSizes = reflowAllKids || wm.IsVerticalRL();
343 nsSize containerSize =
344 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained();
346 nsIFrame* prevKidFrame = nullptr;
347 for (nsTableRowFrame* kidFrame = GetFirstRow(); kidFrame;
348 prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextRow()) {
349 const nscoord rowSpacing =
350 tableFrame->GetRowSpacing(kidFrame->GetRowIndex());
352 // Reflow the row frame
353 if (reflowAllKids || kidFrame->IsSubtreeDirty() ||
354 (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow &&
355 (isPaginated ||
356 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
357 LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize);
358 nsRect oldKidInkOverflow = kidFrame->InkOverflowRect();
360 ReflowOutput kidDesiredSize(aReflowInput.mReflowInput);
362 // Reflow the child into the available space, giving it as much bsize as
363 // it wants. We'll deal with splitting later after we've computed the row
364 // bsizes, taking into account cells with row spans...
365 LogicalSize kidAvailSize = aReflowInput.mAvailSize;
366 kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
367 ReflowInput kidReflowInput(aPresContext, aReflowInput.mReflowInput,
368 kidFrame, kidAvailSize, Nothing(),
369 ReflowInput::InitFlag::CallerWillInit);
370 InitChildReflowInput(aPresContext, borderCollapse, kidReflowInput);
372 // This can indicate that columns were resized.
373 if (aReflowInput.mReflowInput.IsIResize()) {
374 kidReflowInput.SetIResize(true);
377 NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame,
378 "If we're not on the first frame, we should have a "
379 "previous sibling...");
380 // If prev row has nonzero YMost, then we can't be at the top of the page
381 if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) {
382 kidReflowInput.mFlags.mIsTopOfPage = false;
385 LogicalPoint kidPosition(wm, 0, aReflowInput.mBCoord);
386 ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, wm,
387 kidPosition, containerSize, ReflowChildFlags::Default,
388 aStatus);
390 // Place the child
391 PlaceChild(aPresContext, aReflowInput, kidFrame, kidReflowInput, wm,
392 kidPosition, containerSize, kidDesiredSize,
393 oldKidRect.GetPhysicalRect(wm, containerSize),
394 oldKidInkOverflow);
395 aReflowInput.mBCoord += rowSpacing;
397 if (!reflowAllKids) {
398 if (IsSimpleRowFrame(tableFrame, kidFrame)) {
399 // Inform the row of its new bsize.
400 kidFrame->DidResize();
401 // the overflow area may have changed inflate the overflow area
402 const nsStylePosition* stylePos = StylePosition();
403 if (tableFrame->IsAutoBSize(wm) &&
404 !stylePos->BSize(wm).ConvertsToLength()) {
405 // Because other cells in the row may need to be aligned
406 // differently, repaint the entire row
407 InvalidateFrame();
408 } else if (oldKidRect.BSize(wm) != kidDesiredSize.BSize(wm)) {
409 needToCalcRowBSizes = true;
411 } else {
412 needToCalcRowBSizes = true;
416 if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) {
417 nsTableRowFrame* nextRow = kidFrame->GetNextRow();
418 if (nextRow) {
419 *aPageBreakBeforeEnd =
420 nsTableFrame::PageBreakAfter(kidFrame, nextRow);
423 } else {
424 // Move a child that was skipped during a reflow.
425 const LogicalPoint oldPosition =
426 kidFrame->GetLogicalNormalPosition(wm, containerSize);
427 if (oldPosition.B(wm) != aReflowInput.mBCoord) {
428 kidFrame->InvalidateFrameSubtree();
429 const LogicalPoint offset(wm, 0,
430 aReflowInput.mBCoord - oldPosition.B(wm));
431 kidFrame->MovePositionBy(wm, offset);
432 nsTableFrame::RePositionViews(kidFrame);
433 kidFrame->InvalidateFrameSubtree();
436 // Adjust the running b-offset so we know where the next row should be
437 // placed
438 nscoord bSize = kidFrame->BSize(wm) + rowSpacing;
439 aReflowInput.mBCoord += bSize;
441 if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm)) {
442 aReflowInput.mAvailSize.BSize(wm) -= bSize;
445 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
448 if (GetFirstRow()) {
449 aReflowInput.mBCoord -=
450 tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount());
453 // Return our desired rect
454 aDesiredSize.ISize(wm) = aReflowInput.mReflowInput.AvailableISize();
455 aDesiredSize.BSize(wm) = aReflowInput.mBCoord;
457 if (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow) {
458 DidResizeRows(aDesiredSize);
459 if (isPaginated) {
460 CacheRowBSizesForPrinting(GetFirstRow(), wm);
462 } else if (needToCalcRowBSizes) {
463 CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.mReflowInput);
464 if (!reflowAllKids) {
465 InvalidateFrame();
470 nsTableRowFrame* nsTableRowGroupFrame::GetFirstRow() const {
471 nsIFrame* firstChild = mFrames.FirstChild();
472 MOZ_ASSERT(
473 !firstChild || static_cast<nsTableRowFrame*>(do_QueryFrame(firstChild)),
474 "How do we have a non-row child?");
475 return static_cast<nsTableRowFrame*>(firstChild);
478 nsTableRowFrame* nsTableRowGroupFrame::GetLastRow() const {
479 nsIFrame* lastChild = mFrames.LastChild();
480 MOZ_ASSERT(
481 !lastChild || static_cast<nsTableRowFrame*>(do_QueryFrame(lastChild)),
482 "How do we have a non-row child?");
483 return static_cast<nsTableRowFrame*>(lastChild);
486 struct RowInfo {
487 RowInfo() { bSize = pctBSize = hasStyleBSize = hasPctBSize = isSpecial = 0; }
488 unsigned bSize; // content bsize or fixed bsize, excluding pct bsize
489 unsigned pctBSize : 29; // pct bsize
490 unsigned hasStyleBSize : 1;
491 unsigned hasPctBSize : 1;
492 unsigned isSpecial : 1; // there is no cell originating in the row with
493 // rowspan=1 and there are at least 2 cells spanning
494 // the row and there is no style bsize on the row
497 static void UpdateBSizes(RowInfo& aRowInfo, nscoord aAdditionalBSize,
498 nscoord& aTotal, nscoord& aUnconstrainedTotal) {
499 aRowInfo.bSize += aAdditionalBSize;
500 aTotal += aAdditionalBSize;
501 if (!aRowInfo.hasStyleBSize) {
502 aUnconstrainedTotal += aAdditionalBSize;
506 void nsTableRowGroupFrame::DidResizeRows(ReflowOutput& aDesiredSize) {
507 // Update the cells spanning rows with their new bsizes.
508 // This is the place where all of the cells in the row get set to the bsize
509 // of the row.
510 // Reset the overflow area.
511 aDesiredSize.mOverflowAreas.Clear();
512 for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame;
513 rowFrame = rowFrame->GetNextRow()) {
514 rowFrame->DidResize();
515 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame);
519 // This calculates the bsize of all the rows and takes into account
520 // style bsize on the row group, style bsizes on rows and cells, style bsizes on
521 // rowspans. Actual row bsizes will be adjusted later if the table has a style
522 // bsize. Even if rows don't change bsize, this method must be called to set the
523 // bsizes of each cell in the row to the bsize of its row.
524 void nsTableRowGroupFrame::CalculateRowBSizes(nsPresContext* aPresContext,
525 ReflowOutput& aDesiredSize,
526 const ReflowInput& aReflowInput) {
527 nsTableFrame* tableFrame = GetTableFrame();
528 const bool isPaginated = aPresContext->IsPaginated();
530 int32_t numEffCols = tableFrame->GetEffectiveColCount();
532 int32_t startRowIndex = GetStartRowIndex();
533 // find the row corresponding to the row index we just found
534 nsTableRowFrame* startRowFrame = GetFirstRow();
536 if (!startRowFrame) {
537 return;
540 // The current row group block-size is the block-origin of the 1st row
541 // we are about to calculate a block-size for.
542 WritingMode wm = aReflowInput.GetWritingMode();
543 nsSize containerSize; // actual value is unimportant as we're initially
544 // computing sizes, not physical positions
545 nscoord startRowGroupBSize =
546 startRowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm);
548 int32_t numRows =
549 GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex());
550 // Collect the current bsize of each row.
551 if (numRows <= 0) {
552 return;
555 AutoTArray<RowInfo, 32> rowInfo;
556 // XXX(Bug 1631371) Check if this should use a fallible operation as it
557 // pretended earlier.
558 rowInfo.AppendElements(numRows);
560 bool hasRowSpanningCell = false;
561 nscoord bSizeOfRows = 0;
562 nscoord bSizeOfUnStyledRows = 0;
563 // Get the bsize of each row without considering rowspans. This will be the
564 // max of the largest desired bsize of each cell, the largest style bsize of
565 // each cell, the style bsize of the row.
566 nscoord pctBSizeBasis = GetBSizeBasis(aReflowInput);
567 int32_t
568 rowIndex; // the index in rowInfo, not among the rows in the row group
569 nsTableRowFrame* rowFrame;
570 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
571 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
572 nscoord nonPctBSize = rowFrame->GetContentBSize();
573 if (isPaginated) {
574 nonPctBSize = std::max(nonPctBSize, rowFrame->BSize(wm));
576 if (!rowFrame->GetPrevInFlow()) {
577 if (rowFrame->HasPctBSize()) {
578 rowInfo[rowIndex].hasPctBSize = true;
579 rowInfo[rowIndex].pctBSize = rowFrame->GetInitialBSize(pctBSizeBasis);
581 rowInfo[rowIndex].hasStyleBSize = rowFrame->HasStyleBSize();
582 nonPctBSize = std::max(nonPctBSize, rowFrame->GetFixedBSize());
584 UpdateBSizes(rowInfo[rowIndex], nonPctBSize, bSizeOfRows,
585 bSizeOfUnStyledRows);
587 if (!rowInfo[rowIndex].hasStyleBSize) {
588 if (isPaginated ||
589 tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) {
590 rowInfo[rowIndex].isSpecial = true;
591 // iteratate the row's cell frames to see if any do not have rowspan > 1
592 nsTableCellFrame* cellFrame = rowFrame->GetFirstCell();
593 while (cellFrame) {
594 int32_t rowSpan = tableFrame->GetEffectiveRowSpan(
595 rowIndex + startRowIndex, *cellFrame);
596 if (1 == rowSpan) {
597 rowInfo[rowIndex].isSpecial = false;
598 break;
600 cellFrame = cellFrame->GetNextCell();
604 // See if a cell spans into the row. If so we'll have to do the next step
605 if (!hasRowSpanningCell) {
606 if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) {
607 hasRowSpanningCell = true;
612 if (hasRowSpanningCell) {
613 // Get the bsize of cells with rowspans and allocate any extra space to the
614 // rows they span iteratate the child frames and process the row frames
615 // among them
616 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
617 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
618 // See if the row has an originating cell with rowspan > 1. We cannot
619 // determine this for a row in a continued row group by calling
620 // RowHasSpanningCells, because the row's fif may not have any originating
621 // cells yet the row may have a continued cell which originates in it.
622 if (GetPrevInFlow() || tableFrame->RowHasSpanningCells(
623 startRowIndex + rowIndex, numEffCols)) {
624 nsTableCellFrame* cellFrame = rowFrame->GetFirstCell();
625 // iteratate the row's cell frames
626 while (cellFrame) {
627 const nscoord rowSpacing =
628 tableFrame->GetRowSpacing(startRowIndex + rowIndex);
629 int32_t rowSpan = tableFrame->GetEffectiveRowSpan(
630 rowIndex + startRowIndex, *cellFrame);
631 if ((rowIndex + rowSpan) > numRows) {
632 // there might be rows pushed already to the nextInFlow
633 rowSpan = numRows - rowIndex;
635 if (rowSpan > 1) { // a cell with rowspan > 1, determine the bsize of
636 // the rows it spans
637 nscoord bsizeOfRowsSpanned = 0;
638 nscoord bsizeOfUnStyledRowsSpanned = 0;
639 nscoord numSpecialRowsSpanned = 0;
640 nscoord cellSpacingTotal = 0;
641 int32_t spanX;
642 for (spanX = 0; spanX < rowSpan; spanX++) {
643 bsizeOfRowsSpanned += rowInfo[rowIndex + spanX].bSize;
644 if (!rowInfo[rowIndex + spanX].hasStyleBSize) {
645 bsizeOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].bSize;
647 if (0 != spanX) {
648 cellSpacingTotal += rowSpacing;
650 if (rowInfo[rowIndex + spanX].isSpecial) {
651 numSpecialRowsSpanned++;
654 nscoord bsizeOfAreaSpanned = bsizeOfRowsSpanned + cellSpacingTotal;
655 // get the bsize of the cell
656 LogicalSize cellFrameSize = cellFrame->GetLogicalSize(wm);
657 LogicalSize cellDesSize = cellFrame->GetDesiredSize();
658 cellDesSize.BSize(wm) = rowFrame->CalcCellActualBSize(
659 cellFrame, cellDesSize.BSize(wm), wm);
660 cellFrameSize.BSize(wm) = cellDesSize.BSize(wm);
662 if (bsizeOfAreaSpanned < cellFrameSize.BSize(wm)) {
663 // the cell's bsize is larger than the available space of the rows
664 // it spans so distribute the excess bsize to the rows affected
665 nscoord extra = cellFrameSize.BSize(wm) - bsizeOfAreaSpanned;
666 nscoord extraUsed = 0;
667 if (0 == numSpecialRowsSpanned) {
668 // NS_ASSERTION(bsizeOfRowsSpanned > 0, "invalid row span
669 // situation");
670 bool haveUnStyledRowsSpanned = (bsizeOfUnStyledRowsSpanned > 0);
671 nscoord divisor = (haveUnStyledRowsSpanned)
672 ? bsizeOfUnStyledRowsSpanned
673 : bsizeOfRowsSpanned;
674 if (divisor > 0) {
675 for (spanX = rowSpan - 1; spanX >= 0; spanX--) {
676 if (!haveUnStyledRowsSpanned ||
677 !rowInfo[rowIndex + spanX].hasStyleBSize) {
678 // The amount of additional space each row gets is
679 // proportional to its bsize
680 float percent = ((float)rowInfo[rowIndex + spanX].bSize) /
681 ((float)divisor);
683 // give rows their percentage, except for the first row
684 // which gets the remainder
685 nscoord extraForRow =
686 (0 == spanX)
687 ? extra - extraUsed
688 : NSToCoordRound(((float)(extra)) * percent);
689 extraForRow = std::min(extraForRow, extra - extraUsed);
690 // update the row bsize
691 UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow,
692 bSizeOfRows, bSizeOfUnStyledRows);
693 extraUsed += extraForRow;
694 if (extraUsed >= extra) {
695 NS_ASSERTION((extraUsed == extra),
696 "invalid row bsize calculation");
697 break;
701 } else {
702 // put everything in the last row
703 UpdateBSizes(rowInfo[rowIndex + rowSpan - 1], extra,
704 bSizeOfRows, bSizeOfUnStyledRows);
706 } else {
707 // give the extra to the special rows
708 nscoord numSpecialRowsAllocated = 0;
709 for (spanX = rowSpan - 1; spanX >= 0; spanX--) {
710 if (rowInfo[rowIndex + spanX].isSpecial) {
711 // The amount of additional space each degenerate row gets
712 // is proportional to the number of them
713 float percent = 1.0f / ((float)numSpecialRowsSpanned);
715 // give rows their percentage, except for the first row
716 // which gets the remainder
717 nscoord extraForRow =
718 (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated)
719 ? extra - extraUsed
720 : NSToCoordRound(((float)(extra)) * percent);
721 extraForRow = std::min(extraForRow, extra - extraUsed);
722 // update the row bsize
723 UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow,
724 bSizeOfRows, bSizeOfUnStyledRows);
725 extraUsed += extraForRow;
726 if (extraUsed >= extra) {
727 NS_ASSERTION((extraUsed == extra),
728 "invalid row bsize calculation");
729 break;
735 } // if (rowSpan > 1)
736 cellFrame = cellFrame->GetNextCell();
737 } // while (cellFrame)
738 } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) {
739 } // while (rowFrame)
742 // pct bsize rows have already got their content bsizes.
743 // Give them their pct bsizes up to pctBSizeBasis
744 nscoord extra = pctBSizeBasis - bSizeOfRows;
745 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0);
746 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
747 RowInfo& rInfo = rowInfo[rowIndex];
748 if (rInfo.hasPctBSize) {
749 nscoord rowExtra =
750 (rInfo.pctBSize > rInfo.bSize) ? rInfo.pctBSize - rInfo.bSize : 0;
751 rowExtra = std::min(rowExtra, extra);
752 UpdateBSizes(rInfo, rowExtra, bSizeOfRows, bSizeOfUnStyledRows);
753 extra -= rowExtra;
757 bool styleBSizeAllocation = false;
758 nscoord rowGroupBSize = startRowGroupBSize + bSizeOfRows +
759 tableFrame->GetRowSpacing(0, numRows - 1);
760 // if we have a style bsize, allocate the extra bsize to unconstrained rows
761 if ((aReflowInput.ComputedBSize() > rowGroupBSize) &&
762 (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize())) {
763 nscoord extraComputedBSize = aReflowInput.ComputedBSize() - rowGroupBSize;
764 nscoord extraUsed = 0;
765 bool haveUnStyledRows = (bSizeOfUnStyledRows > 0);
766 nscoord divisor = (haveUnStyledRows) ? bSizeOfUnStyledRows : bSizeOfRows;
767 if (divisor > 0) {
768 styleBSizeAllocation = true;
769 for (rowIndex = 0; rowIndex < numRows; rowIndex++) {
770 if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleBSize) {
771 // The amount of additional space each row gets is based on the
772 // percentage of space it occupies
773 float percent = ((float)rowInfo[rowIndex].bSize) / ((float)divisor);
774 // give rows their percentage, except for the last row which gets the
775 // remainder
776 nscoord extraForRow =
777 (numRows - 1 == rowIndex)
778 ? extraComputedBSize - extraUsed
779 : NSToCoordRound(((float)extraComputedBSize) * percent);
780 extraForRow = std::min(extraForRow, extraComputedBSize - extraUsed);
781 // update the row bsize
782 UpdateBSizes(rowInfo[rowIndex], extraForRow, bSizeOfRows,
783 bSizeOfUnStyledRows);
784 extraUsed += extraForRow;
785 if (extraUsed >= extraComputedBSize) {
786 NS_ASSERTION((extraUsed == extraComputedBSize),
787 "invalid row bsize calculation");
788 break;
793 rowGroupBSize = aReflowInput.ComputedBSize();
796 if (wm.IsVertical()) {
797 // we need the correct containerSize below for block positioning in
798 // vertical-rl writing mode
799 containerSize.width = rowGroupBSize;
802 nscoord bOrigin = startRowGroupBSize;
803 // update the rows with their (potentially) new bsizes
804 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
805 rowFrame = rowFrame->GetNextRow(), rowIndex++) {
806 nsRect rowBounds = rowFrame->GetRect();
807 LogicalSize rowBoundsSize(wm, rowBounds.Size());
808 nsRect rowInkOverflow = rowFrame->InkOverflowRect();
809 nscoord deltaB =
810 bOrigin - rowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm);
812 nscoord rowBSize =
813 (rowInfo[rowIndex].bSize > 0) ? rowInfo[rowIndex].bSize : 0;
815 if (deltaB != 0 || (rowBSize != rowBoundsSize.BSize(wm))) {
816 // Resize/move the row to its final size and position
817 if (deltaB != 0) {
818 rowFrame->InvalidateFrameSubtree();
821 rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, deltaB));
822 rowFrame->SetSize(LogicalSize(wm, rowBoundsSize.ISize(wm), rowBSize));
824 nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowInkOverflow,
825 false);
827 if (deltaB != 0) {
828 nsTableFrame::RePositionViews(rowFrame);
829 // XXXbz we don't need to update our overflow area?
832 bOrigin += rowBSize + tableFrame->GetRowSpacing(startRowIndex + rowIndex);
835 if (isPaginated && styleBSizeAllocation) {
836 // since the row group has a style bsize, cache the row bsizes,
837 // so next in flows can honor them
838 CacheRowBSizesForPrinting(GetFirstRow(), wm);
841 DidResizeRows(aDesiredSize);
843 aDesiredSize.BSize(wm) = rowGroupBSize; // Adjust our desired size
846 nscoord nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aBTotalOffset,
847 nscoord aISize,
848 WritingMode aWM) {
849 nsTableFrame* tableFrame = GetTableFrame();
850 nsSize containerSize = tableFrame->GetSize();
851 const nsStyleVisibility* groupVis = StyleVisibility();
852 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
853 if (collapseGroup) {
854 tableFrame->SetNeedToCollapse(true);
857 OverflowAreas overflow;
859 nsTableRowFrame* rowFrame = GetFirstRow();
860 bool didCollapse = false;
861 nscoord bGroupOffset = 0;
862 while (rowFrame) {
863 bGroupOffset += rowFrame->CollapseRowIfNecessary(
864 bGroupOffset, aISize, collapseGroup, didCollapse);
865 ConsiderChildOverflow(overflow, rowFrame);
866 rowFrame = rowFrame->GetNextRow();
869 LogicalRect groupRect = GetLogicalRect(aWM, containerSize);
870 nsRect oldGroupRect = GetRect();
871 nsRect oldGroupInkOverflow = InkOverflowRect();
873 groupRect.BSize(aWM) -= bGroupOffset;
874 if (didCollapse) {
875 // add back the cellspacing between rowgroups
876 groupRect.BSize(aWM) +=
877 tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount());
880 groupRect.BStart(aWM) -= aBTotalOffset;
881 groupRect.ISize(aWM) = aISize;
883 if (aBTotalOffset != 0) {
884 InvalidateFrameSubtree();
887 SetRect(aWM, groupRect, containerSize);
888 overflow.UnionAllWith(
889 nsRect(0, 0, groupRect.Width(aWM), groupRect.Height(aWM)));
890 FinishAndStoreOverflow(overflow, groupRect.Size(aWM).GetPhysicalSize(aWM));
891 nsTableFrame::RePositionViews(this);
892 nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupInkOverflow,
893 false);
895 return bGroupOffset;
898 nsTableRowFrame* nsTableRowGroupFrame::CreateContinuingRowFrame(
899 nsIFrame* aRowFrame) {
900 // Create the continuing frame which will create continuing cell frames.
901 auto* contRowFrame = static_cast<nsTableRowFrame*>(
902 PresShell()->FrameConstructor()->CreateContinuingFrame(aRowFrame, this));
904 // Add the continuing row frame to the child list.
905 mFrames.InsertFrame(nullptr, aRowFrame, contRowFrame);
907 // Push the continuing row frame and the frames that follow.
908 // This needs to match `UndoContinuedRow`.
909 PushChildrenToOverflow(contRowFrame, aRowFrame);
911 return contRowFrame;
914 // Reflow the cells with rowspan > 1 which originate between aFirstRow
915 // and end on or after aLastRow. aFirstTruncatedRow is the highest row on the
916 // page that contains a cell which cannot split on this page
917 void nsTableRowGroupFrame::SplitSpanningCells(
918 nsPresContext* aPresContext, const ReflowInput& aReflowInput,
919 nsTableFrame* aTable, nsTableRowFrame* aFirstRow, nsTableRowFrame* aLastRow,
920 bool aFirstRowIsTopOfPage, nscoord aSpanningRowBEnd,
921 const nsSize& aContainerSize, nsTableRowFrame*& aContRow,
922 nsTableRowFrame*& aFirstTruncatedRow, nscoord& aDesiredBSize) {
923 NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes");
924 aFirstTruncatedRow = nullptr;
925 aDesiredBSize = 0;
927 const WritingMode wm = aReflowInput.GetWritingMode();
928 const bool borderCollapse = aTable->IsBorderCollapse();
929 int32_t lastRowIndex = aLastRow->GetRowIndex();
930 bool wasLast = false;
931 bool haveRowSpan = false;
932 // Iterate the rows between aFirstRow and aLastRow
933 for (nsTableRowFrame* row = aFirstRow; !wasLast; row = row->GetNextRow()) {
934 wasLast = (row == aLastRow);
935 int32_t rowIndex = row->GetRowIndex();
936 const LogicalRect rowRect = row->GetLogicalNormalRect(wm, aContainerSize);
937 // Iterate the cells looking for those that have rowspan > 1
938 for (nsTableCellFrame* cell = row->GetFirstCell(); cell;
939 cell = cell->GetNextCell()) {
940 int32_t rowSpan = aTable->GetEffectiveRowSpan(rowIndex, *cell);
941 // Only reflow rowspan > 1 cells which span aLastRow. Those which don't
942 // span aLastRow were reflowed correctly during the unconstrained bsize
943 // reflow.
944 if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) {
945 haveRowSpan = true;
946 nsReflowStatus status;
947 // Ask the row to reflow the cell to the bsize of all the rows it spans
948 // up through aLastRow cellAvailBSize is the space between the row group
949 // start and the end of the page
950 const nscoord cellAvailBSize = aSpanningRowBEnd - rowRect.BStart(wm);
951 NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?");
952 bool isTopOfPage = (row == aFirstRow) && aFirstRowIsTopOfPage;
954 LogicalSize rowAvailSize(
955 wm, aReflowInput.AvailableISize(),
956 std::max(aReflowInput.AvailableBSize() - rowRect.BStart(wm), 0));
957 // Don't let the available block-size exceed what CalculateRowBSizes set
958 // for it.
959 rowAvailSize.BSize(wm) =
960 std::min(rowAvailSize.BSize(wm), rowRect.BSize(wm));
961 ReflowInput rowReflowInput(
962 aPresContext, aReflowInput, row,
963 rowAvailSize.ConvertTo(row->GetWritingMode(), wm), Nothing(),
964 ReflowInput::InitFlag::CallerWillInit);
965 InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput);
966 rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page
968 nscoord cellBSize =
969 row->ReflowCellFrame(aPresContext, rowReflowInput, isTopOfPage,
970 cell, cellAvailBSize, status);
971 aDesiredBSize = std::max(aDesiredBSize, rowRect.BStart(wm) + cellBSize);
972 if (status.IsComplete()) {
973 if (cellBSize > cellAvailBSize) {
974 aFirstTruncatedRow = row;
975 if ((row != aFirstRow) || !aFirstRowIsTopOfPage) {
976 // return now, since we will be getting another reflow after
977 // either (1) row is moved to the next page or (2) the row group
978 // is moved to the next page
979 return;
982 } else {
983 if (!aContRow) {
984 aContRow = CreateContinuingRowFrame(aLastRow);
986 if (aContRow) {
987 if (row != aLastRow) {
988 // aContRow needs a continuation for cell, since cell spanned into
989 // aLastRow but does not originate there
990 nsTableCellFrame* contCell = static_cast<nsTableCellFrame*>(
991 PresShell()->FrameConstructor()->CreateContinuingFrame(
992 cell, aLastRow));
993 uint32_t colIndex = cell->ColIndex();
994 aContRow->InsertCellFrame(contCell, colIndex);
1001 if (!haveRowSpan) {
1002 aDesiredBSize = aLastRow->GetLogicalNormalRect(wm, aContainerSize).BEnd(wm);
1006 // Remove the next-in-flow of the row, its cells and their cell blocks. This
1007 // is necessary in case the row doesn't need a continuation later on or needs
1008 // a continuation which doesn't have the same number of cells that now exist.
1009 void nsTableRowGroupFrame::UndoContinuedRow(nsPresContext* aPresContext,
1010 nsTableRowFrame* aRow) {
1011 if (!aRow) {
1012 return; // allow null aRow to avoid callers doing null checks
1015 // rowBefore was the prev-sibling of aRow's next-sibling before aRow was
1016 // created
1017 nsTableRowFrame* rowBefore = (nsTableRowFrame*)aRow->GetPrevInFlow();
1018 MOZ_ASSERT(mFrames.ContainsFrame(rowBefore),
1019 "rowBefore not in our frame list?");
1021 // Needs to match `CreateContinuingRowFrame` - we're assuming that continued
1022 // frames always go into overflow frames list.
1023 AutoFrameListPtr overflows(aPresContext, StealOverflowFrames());
1024 if (!rowBefore || !overflows || overflows->IsEmpty() ||
1025 overflows->FirstChild() != aRow) {
1026 NS_ERROR("invalid continued row");
1027 return;
1030 DestroyContext context(aPresContext->PresShell());
1031 // Destroy aRow, its cells, and their cell blocks. Cell blocks that have split
1032 // will not have reflowed yet to pick up content from any overflow lines.
1033 overflows->DestroyFrame(context, aRow);
1035 // Put the overflow rows into our child list
1036 if (!overflows->IsEmpty()) {
1037 mFrames.InsertFrames(nullptr, rowBefore, std::move(*overflows));
1041 void nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext,
1042 ReflowOutput& aDesiredSize,
1043 const ReflowInput& aReflowInput,
1044 nsTableFrame* aTableFrame,
1045 nsReflowStatus& aStatus,
1046 bool aRowForcedPageBreak) {
1047 MOZ_ASSERT(aPresContext->IsPaginated(),
1048 "SplitRowGroup currently supports only paged media");
1050 const WritingMode wm = aReflowInput.GetWritingMode();
1051 nsTableRowFrame* prevRowFrame = nullptr;
1052 aDesiredSize.BSize(wm) = 0;
1053 aDesiredSize.SetOverflowAreasToDesiredBounds();
1055 const nscoord availISize = aReflowInput.AvailableISize();
1056 const nscoord availBSize = aReflowInput.AvailableBSize();
1057 const nsSize containerSize =
1058 aReflowInput.ComputedSizeAsContainerIfConstrained();
1059 const bool borderCollapse = aTableFrame->IsBorderCollapse();
1061 const nscoord pageBSize =
1062 LogicalSize(wm, aPresContext->GetPageSize()).BSize(wm);
1063 NS_ASSERTION(pageBSize != NS_UNCONSTRAINEDSIZE,
1064 "The table shouldn't be split when there should be space");
1066 bool isTopOfPage = aReflowInput.mFlags.mIsTopOfPage;
1067 nsTableRowFrame* firstRowThisPage = GetFirstRow();
1069 // Need to dirty the table's geometry, or else the row might skip
1070 // reflowing its cell as an optimization.
1071 aTableFrame->SetGeometryDirty();
1073 // Walk each of the row frames looking for the first row frame that doesn't
1074 // fit in the available space
1075 for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame;
1076 rowFrame = rowFrame->GetNextRow()) {
1077 bool rowIsOnPage = true;
1078 const nscoord rowSpacing =
1079 aTableFrame->GetRowSpacing(rowFrame->GetRowIndex());
1080 const LogicalRect rowRect =
1081 rowFrame->GetLogicalNormalRect(wm, containerSize);
1082 // See if the row fits on this page
1083 if (rowRect.BEnd(wm) > availBSize) {
1084 nsTableRowFrame* contRow = nullptr;
1085 // Reflow the row in the availabe space and have it split if it is the 1st
1086 // row (on the page) or there is at least 5% of the current page available
1087 // XXX this 5% should be made a preference
1088 if (!prevRowFrame ||
1089 (availBSize - aDesiredSize.BSize(wm) > pageBSize / 20)) {
1090 LogicalSize availSize(wm, availISize,
1091 std::max(availBSize - rowRect.BStart(wm), 0));
1092 // Don't let the available block-size exceed what CalculateRowBSizes set
1093 // for it.
1094 availSize.BSize(wm) = std::min(availSize.BSize(wm), rowRect.BSize(wm));
1096 ReflowInput rowReflowInput(
1097 aPresContext, aReflowInput, rowFrame,
1098 availSize.ConvertTo(rowFrame->GetWritingMode(), wm), Nothing(),
1099 ReflowInput::InitFlag::CallerWillInit);
1101 InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput);
1102 rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page
1103 ReflowOutput rowMetrics(aReflowInput);
1105 // Get the old size before we reflow.
1106 nsRect oldRowRect = rowFrame->GetRect();
1107 nsRect oldRowInkOverflow = rowFrame->InkOverflowRect();
1109 // Reflow the cell with the constrained bsize. A cell with rowspan >1
1110 // will get this reflow later during SplitSpanningCells.
1112 // Note: We just pass dummy aPos and aContainerSize since we are not
1113 // moving the row frame.
1114 const LogicalPoint dummyPos(wm);
1115 const nsSize dummyContainerSize;
1116 ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput, wm,
1117 dummyPos, dummyContainerSize, ReflowChildFlags::NoMoveFrame,
1118 aStatus);
1119 FinishReflowChild(rowFrame, aPresContext, rowMetrics, &rowReflowInput,
1120 wm, dummyPos, dummyContainerSize,
1121 ReflowChildFlags::NoMoveFrame);
1122 rowFrame->DidResize(ForceAlignTopForTableCell::Yes);
1124 if (!aRowForcedPageBreak && !aStatus.IsFullyComplete() &&
1125 ShouldAvoidBreakInside(aReflowInput)) {
1126 aStatus.SetInlineLineBreakBeforeAndReset();
1127 break;
1130 nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect,
1131 oldRowInkOverflow, false);
1133 if (aStatus.IsIncomplete()) {
1134 // The row frame is incomplete and all of the rowspan 1 cells' block
1135 // frames split
1136 if ((rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize()) ||
1137 isTopOfPage) {
1138 // The row stays on this page because either it split ok or we're on
1139 // the top of page. If top of page and the block-size exceeded the
1140 // avail block-size, then there will be data loss.
1141 NS_ASSERTION(
1142 rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize(),
1143 "Data loss - incomplete row needed more block-size than "
1144 "available, on top of page!");
1145 contRow = CreateContinuingRowFrame(rowFrame);
1146 aDesiredSize.BSize(wm) += rowMetrics.BSize(wm);
1147 if (prevRowFrame) {
1148 aDesiredSize.BSize(wm) += rowSpacing;
1150 } else {
1151 // Put the row on the next page to give it more block-size.
1152 rowIsOnPage = false;
1154 } else {
1155 // The row frame is complete because either (1) its minimum block-size
1156 // is greater than the available block-size we gave it, or (2) it may
1157 // have been given a larger block-size through style than its content,
1158 // or (3) it contains a rowspan >1 cell which hasn't been reflowed
1159 // with a constrained block-size yet (we will find out when
1160 // SplitSpanningCells is called below)
1161 if (rowMetrics.BSize(wm) > availSize.BSize(wm) ||
1162 (aStatus.IsInlineBreakBefore() && !aRowForcedPageBreak)) {
1163 // cases (1) and (2)
1164 if (isTopOfPage) {
1165 // We're on top of the page, so keep the row on this page. There
1166 // will be data loss. Push the row frame that follows
1167 nsTableRowFrame* nextRowFrame = rowFrame->GetNextRow();
1168 if (nextRowFrame) {
1169 aStatus.Reset();
1170 aStatus.SetIncomplete();
1172 aDesiredSize.BSize(wm) += rowMetrics.BSize(wm);
1173 if (prevRowFrame) {
1174 aDesiredSize.BSize(wm) += rowSpacing;
1176 NS_WARNING(
1177 "Data loss - complete row needed more block-size than "
1178 "available, on top of page");
1179 } else {
1180 // We're not on top of the page, so put the row on the next page
1181 // to give it more block-size.
1182 rowIsOnPage = false;
1186 } else {
1187 // Put the row on the next page to give it more block-size.
1188 rowIsOnPage = false;
1191 nsTableRowFrame* lastRowThisPage = rowFrame;
1192 nscoord spanningRowBEnd = availBSize;
1193 if (!rowIsOnPage) {
1194 NS_ASSERTION(!contRow,
1195 "We should not have created a continuation if none of "
1196 "this row fits");
1197 if (!prevRowFrame ||
1198 (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput))) {
1199 aStatus.SetInlineLineBreakBeforeAndReset();
1200 break;
1202 spanningRowBEnd =
1203 prevRowFrame->GetLogicalNormalRect(wm, containerSize).BEnd(wm);
1204 lastRowThisPage = prevRowFrame;
1205 aStatus.Reset();
1206 aStatus.SetIncomplete();
1209 // reflow the cells with rowspan >1 that occur on the page
1210 nsTableRowFrame* firstTruncatedRow;
1211 nscoord bMost;
1212 SplitSpanningCells(aPresContext, aReflowInput, aTableFrame,
1213 firstRowThisPage, lastRowThisPage,
1214 aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd,
1215 containerSize, contRow, firstTruncatedRow, bMost);
1216 if (firstTruncatedRow) {
1217 // A rowspan >1 cell did not fit (and could not split) in the space we
1218 // gave it
1219 if (firstTruncatedRow == firstRowThisPage) {
1220 if (aReflowInput.mFlags.mIsTopOfPage) {
1221 NS_WARNING("data loss in a row spanned cell");
1222 } else {
1223 // We can't push children, so let our parent reflow us again with
1224 // more space
1225 aDesiredSize.BSize(wm) = rowRect.BEnd(wm);
1226 aStatus.Reset();
1227 UndoContinuedRow(aPresContext, contRow);
1228 contRow = nullptr;
1230 } else {
1231 // Try to put firstTruncateRow on the next page
1232 nsTableRowFrame* rowBefore = firstTruncatedRow->GetPrevRow();
1233 const nscoord oldSpanningRowBEnd = spanningRowBEnd;
1234 spanningRowBEnd =
1235 rowBefore->GetLogicalNormalRect(wm, containerSize).BEnd(wm);
1237 UndoContinuedRow(aPresContext, contRow);
1238 contRow = nullptr;
1239 nsTableRowFrame* oldLastRowThisPage = lastRowThisPage;
1240 lastRowThisPage = rowBefore;
1241 aStatus.Reset();
1242 aStatus.SetIncomplete();
1244 // Call SplitSpanningCells again with rowBefore as the last row on the
1245 // page
1246 SplitSpanningCells(aPresContext, aReflowInput, aTableFrame,
1247 firstRowThisPage, rowBefore,
1248 aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd,
1249 containerSize, contRow, firstTruncatedRow,
1250 aDesiredSize.BSize(wm));
1251 if (firstTruncatedRow) {
1252 if (aReflowInput.mFlags.mIsTopOfPage) {
1253 // We were better off with the 1st call to SplitSpanningCells, do
1254 // it again
1255 UndoContinuedRow(aPresContext, contRow);
1256 contRow = nullptr;
1257 lastRowThisPage = oldLastRowThisPage;
1258 spanningRowBEnd = oldSpanningRowBEnd;
1259 SplitSpanningCells(aPresContext, aReflowInput, aTableFrame,
1260 firstRowThisPage, lastRowThisPage,
1261 aReflowInput.mFlags.mIsTopOfPage,
1262 spanningRowBEnd, containerSize, contRow,
1263 firstTruncatedRow, aDesiredSize.BSize(wm));
1264 NS_WARNING("data loss in a row spanned cell");
1265 } else {
1266 // Let our parent reflow us again with more space
1267 aDesiredSize.BSize(wm) = rowRect.BEnd(wm);
1268 aStatus.Reset();
1269 UndoContinuedRow(aPresContext, contRow);
1270 contRow = nullptr;
1274 } else {
1275 aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bMost);
1276 if (contRow) {
1277 aStatus.Reset();
1278 aStatus.SetIncomplete();
1281 if (aStatus.IsIncomplete() && !contRow) {
1282 if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) {
1283 PushChildrenToOverflow(nextRow, lastRowThisPage);
1285 } else if (aStatus.IsComplete() && lastRowThisPage) {
1286 // Our size from the unconstrained reflow exceeded the constrained
1287 // available space but our size in the constrained reflow is Complete.
1288 // This can happen when a non-zero block-end margin is suppressed in
1289 // nsBlockFrame::ComputeFinalSize.
1290 if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) {
1291 aStatus.Reset();
1292 aStatus.SetIncomplete();
1293 PushChildrenToOverflow(nextRow, lastRowThisPage);
1296 break;
1298 aDesiredSize.BSize(wm) = rowRect.BEnd(wm);
1299 prevRowFrame = rowFrame;
1300 // see if there is a page break after the row
1301 nsTableRowFrame* nextRow = rowFrame->GetNextRow();
1302 if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) {
1303 PushChildrenToOverflow(nextRow, rowFrame);
1304 aStatus.Reset();
1305 aStatus.SetIncomplete();
1306 break;
1308 // After the 1st row that has a block-size, we can't be on top of the page
1309 // anymore.
1310 isTopOfPage = isTopOfPage && rowRect.BEnd(wm) == 0;
1314 /** Layout the entire row group.
1315 * This method stacks rows vertically according to HTML 4.0 rules.
1316 * Rows are responsible for layout of their children.
1318 void nsTableRowGroupFrame::Reflow(nsPresContext* aPresContext,
1319 ReflowOutput& aDesiredSize,
1320 const ReflowInput& aReflowInput,
1321 nsReflowStatus& aStatus) {
1322 MarkInReflow();
1323 DO_GLOBAL_REFLOW_COUNT("nsTableRowGroupFrame");
1324 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1326 // Row geometry may be going to change so we need to invalidate any row
1327 // cursor.
1328 ClearRowCursor();
1330 // see if a special bsize reflow needs to occur due to having a pct bsize
1331 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
1333 nsTableFrame* tableFrame = GetTableFrame();
1334 TableRowGroupReflowInput state(aReflowInput);
1335 const nsStyleVisibility* groupVis = StyleVisibility();
1336 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
1337 if (collapseGroup) {
1338 tableFrame->SetNeedToCollapse(true);
1341 // Check for an overflow list
1342 MoveOverflowToChildList();
1344 // Reflow the existing frames.
1345 bool splitDueToPageBreak = false;
1346 ReflowChildren(aPresContext, aDesiredSize, state, aStatus,
1347 &splitDueToPageBreak);
1349 // See if all the frames fit. Do not try to split anything if we're
1350 // not paginated ... we can't split across columns yet.
1351 WritingMode wm = aReflowInput.GetWritingMode();
1352 if (aReflowInput.mFlags.mTableIsSplittable &&
1353 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1354 (aStatus.IsIncomplete() || splitDueToPageBreak ||
1355 aDesiredSize.BSize(wm) > aReflowInput.AvailableBSize())) {
1356 // Nope, find a place to split the row group
1357 auto& mutableRIFlags = const_cast<ReflowInput::Flags&>(aReflowInput.mFlags);
1358 const bool savedSpecialBSizeReflow = mutableRIFlags.mSpecialBSizeReflow;
1359 mutableRIFlags.mSpecialBSizeReflow = false;
1361 SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus,
1362 splitDueToPageBreak);
1364 mutableRIFlags.mSpecialBSizeReflow = savedSpecialBSizeReflow;
1367 // XXXmats The following is just bogus. We leave it here for now because
1368 // ReflowChildren should pull up rows from our next-in-flow before returning
1369 // a Complete status, but doesn't (bug 804888).
1370 if (GetNextInFlow() && GetNextInFlow()->PrincipalChildList().FirstChild()) {
1371 aStatus.SetIncomplete();
1374 SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) &&
1375 (aReflowInput.ComputedBSize() > 0));
1377 // Just set our isize to what was available.
1378 // The table will calculate the isize and not use our value.
1379 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
1381 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
1383 // If our parent is in initial reflow, it'll handle invalidating our
1384 // entire overflow rect.
1385 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
1386 aDesiredSize.Size(wm) != GetLogicalSize(wm)) {
1387 InvalidateFrame();
1390 FinishAndStoreOverflow(&aDesiredSize);
1392 // Any absolutely-positioned children will get reflowed in
1393 // nsIFrame::FixupPositionedTableParts in another pass, so propagate our
1394 // dirtiness to them before our parent clears our dirty bits.
1395 PushDirtyBitToAbsoluteFrames();
1398 bool nsTableRowGroupFrame::ComputeCustomOverflow(
1399 OverflowAreas& aOverflowAreas) {
1400 // Row cursor invariants depend on the ink overflow area of the rows,
1401 // which may have changed, so we need to clear the cursor now.
1402 ClearRowCursor();
1403 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
1406 /* virtual */
1407 void nsTableRowGroupFrame::DidSetComputedStyle(
1408 ComputedStyle* aOldComputedStyle) {
1409 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
1410 nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle);
1412 if (!aOldComputedStyle) {
1413 return; // avoid the following on init
1416 nsTableFrame* tableFrame = GetTableFrame();
1417 if (tableFrame->IsBorderCollapse() &&
1418 tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) {
1419 TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(),
1420 GetRowCount());
1421 tableFrame->AddBCDamageArea(damageArea);
1425 void nsTableRowGroupFrame::AppendFrames(ChildListID aListID,
1426 nsFrameList&& aFrameList) {
1427 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1429 DrainSelfOverflowList(); // ensure the last frame is in mFrames
1430 ClearRowCursor();
1432 // collect the new row frames in an array
1433 // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
1434 AutoTArray<nsTableRowFrame*, 8> rows;
1435 for (nsIFrame* f : aFrameList) {
1436 nsTableRowFrame* rowFrame = do_QueryFrame(f);
1437 NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
1438 if (rowFrame) {
1439 NS_ASSERTION(
1440 mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay,
1441 "wrong display type on rowframe");
1442 rows.AppendElement(rowFrame);
1446 int32_t rowIndex = GetRowCount();
1447 // Append the frames to the sibling chain
1448 mFrames.AppendFrames(nullptr, std::move(aFrameList));
1450 if (rows.Length() > 0) {
1451 nsTableFrame* tableFrame = GetTableFrame();
1452 tableFrame->AppendRows(this, rowIndex, rows);
1453 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1454 NS_FRAME_HAS_DIRTY_CHILDREN);
1455 tableFrame->SetGeometryDirty();
1459 void nsTableRowGroupFrame::InsertFrames(
1460 ChildListID aListID, nsIFrame* aPrevFrame,
1461 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
1462 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1463 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
1464 "inserting after sibling frame with different parent");
1466 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
1467 ClearRowCursor();
1469 // collect the new row frames in an array
1470 // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
1471 nsTableFrame* tableFrame = GetTableFrame();
1472 nsTArray<nsTableRowFrame*> rows;
1473 bool gotFirstRow = false;
1474 for (nsIFrame* f : aFrameList) {
1475 nsTableRowFrame* rowFrame = do_QueryFrame(f);
1476 NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
1477 if (rowFrame) {
1478 NS_ASSERTION(
1479 mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay,
1480 "wrong display type on rowframe");
1481 rows.AppendElement(rowFrame);
1482 if (!gotFirstRow) {
1483 rowFrame->SetFirstInserted(true);
1484 gotFirstRow = true;
1485 tableFrame->SetRowInserted(true);
1490 int32_t startRowIndex = GetStartRowIndex();
1491 // Insert the frames in the sibling chain
1492 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
1494 int32_t numRows = rows.Length();
1495 if (numRows > 0) {
1496 nsTableRowFrame* prevRow =
1497 (nsTableRowFrame*)nsTableFrame::GetFrameAtOrBefore(
1498 this, aPrevFrame, LayoutFrameType::TableRow);
1499 int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex;
1500 tableFrame->InsertRows(this, rows, rowIndex, true);
1502 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1503 NS_FRAME_HAS_DIRTY_CHILDREN);
1504 tableFrame->SetGeometryDirty();
1508 void nsTableRowGroupFrame::RemoveFrame(DestroyContext& aContext,
1509 ChildListID aListID,
1510 nsIFrame* aOldFrame) {
1511 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1513 ClearRowCursor();
1515 // XXX why are we doing the QI stuff? There shouldn't be any non-rows here.
1516 nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame);
1517 if (rowFrame) {
1518 nsTableFrame* tableFrame = GetTableFrame();
1519 // remove the rows from the table (and flag a rebalance)
1520 tableFrame->RemoveRows(*rowFrame, 1, true);
1522 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1523 NS_FRAME_HAS_DIRTY_CHILDREN);
1524 tableFrame->SetGeometryDirty();
1526 mFrames.DestroyFrame(aContext, aOldFrame);
1529 /* virtual */
1530 nsMargin nsTableRowGroupFrame::GetUsedMargin() const {
1531 return nsMargin(0, 0, 0, 0);
1534 /* virtual */
1535 nsMargin nsTableRowGroupFrame::GetUsedBorder() const {
1536 return nsMargin(0, 0, 0, 0);
1539 /* virtual */
1540 nsMargin nsTableRowGroupFrame::GetUsedPadding() const {
1541 return nsMargin(0, 0, 0, 0);
1544 nscoord nsTableRowGroupFrame::GetBSizeBasis(const ReflowInput& aReflowInput) {
1545 nscoord result = 0;
1546 nsTableFrame* tableFrame = GetTableFrame();
1547 int32_t startRowIndex = GetStartRowIndex();
1548 if ((aReflowInput.ComputedBSize() > 0) &&
1549 (aReflowInput.ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
1550 nscoord cellSpacing = tableFrame->GetRowSpacing(
1551 startRowIndex,
1552 std::max(startRowIndex, startRowIndex + GetRowCount() - 1));
1553 result = aReflowInput.ComputedBSize() - cellSpacing;
1554 } else {
1555 const ReflowInput* parentRI = aReflowInput.mParentReflowInput;
1556 if (parentRI && (tableFrame != parentRI->mFrame)) {
1557 parentRI = parentRI->mParentReflowInput;
1559 if (parentRI && (tableFrame == parentRI->mFrame) &&
1560 (parentRI->ComputedBSize() > 0) &&
1561 (parentRI->ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
1562 nscoord cellSpacing =
1563 tableFrame->GetRowSpacing(-1, tableFrame->GetRowCount());
1564 result = parentRI->ComputedBSize() - cellSpacing;
1568 return result;
1571 bool nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame,
1572 nsTableRowFrame* aRowFrame) {
1573 int32_t rowIndex = aRowFrame->GetRowIndex();
1575 // It's a simple row frame if there are no cells that span into or
1576 // across the row
1577 int32_t numEffCols = aTableFrame->GetEffectiveColCount();
1578 if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) &&
1579 !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) {
1580 return true;
1583 return false;
1586 /** find page break before the first row **/
1587 bool nsTableRowGroupFrame::HasInternalBreakBefore() const {
1588 nsIFrame* firstChild = mFrames.FirstChild();
1589 if (!firstChild) {
1590 return false;
1592 return firstChild->StyleDisplay()->BreakBefore();
1595 /** find page break after the last row **/
1596 bool nsTableRowGroupFrame::HasInternalBreakAfter() const {
1597 nsIFrame* lastChild = mFrames.LastChild();
1598 if (!lastChild) {
1599 return false;
1601 return lastChild->StyleDisplay()->BreakAfter();
1603 /* ----- global methods ----- */
1605 nsTableRowGroupFrame* NS_NewTableRowGroupFrame(PresShell* aPresShell,
1606 ComputedStyle* aStyle) {
1607 return new (aPresShell)
1608 nsTableRowGroupFrame(aStyle, aPresShell->GetPresContext());
1611 NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame)
1613 #ifdef DEBUG_FRAME_DUMP
1614 nsresult nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const {
1615 return MakeFrameName(u"TableRowGroup"_ns, aResult);
1617 #endif
1619 LogicalMargin nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) {
1620 LogicalMargin border(aWM);
1621 nsTableRowFrame* firstRowFrame = GetFirstRow();
1622 if (!firstRowFrame) {
1623 return border;
1625 nsTableRowFrame* lastRowFrame = firstRowFrame;
1626 for (nsTableRowFrame* rowFrame = firstRowFrame->GetNextRow(); rowFrame;
1627 rowFrame = rowFrame->GetNextRow()) {
1628 lastRowFrame = rowFrame;
1630 border.BStart(aWM) = firstRowFrame->GetBStartBCBorderWidth();
1631 border.BEnd(aWM) = lastRowFrame->GetBEndBCBorderWidth();
1632 return border;
1635 // nsILineIterator methods
1636 int32_t nsTableRowGroupFrame::GetNumLines() const { return GetRowCount(); }
1638 bool nsTableRowGroupFrame::IsLineIteratorFlowRTL() {
1639 return StyleDirection::Rtl == GetTableFrame()->StyleVisibility()->mDirection;
1642 Result<nsILineIterator::LineInfo, nsresult> nsTableRowGroupFrame::GetLine(
1643 int32_t aLineNumber) {
1644 if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) {
1645 return Err(NS_ERROR_FAILURE);
1647 LineInfo structure;
1648 nsTableFrame* table = GetTableFrame();
1649 nsTableCellMap* cellMap = table->GetCellMap();
1650 aLineNumber += GetStartRowIndex();
1652 structure.mNumFramesOnLine =
1653 cellMap->GetNumCellsOriginatingInRow(aLineNumber);
1654 if (structure.mNumFramesOnLine == 0) {
1655 return structure;
1657 int32_t colCount = table->GetColCount();
1658 for (int32_t i = 0; i < colCount; i++) {
1659 CellData* data = cellMap->GetDataAt(aLineNumber, i);
1660 if (data && data->IsOrig()) {
1661 structure.mFirstFrameOnLine = (nsIFrame*)data->GetCellFrame();
1662 nsIFrame* parent = structure.mFirstFrameOnLine->GetParent();
1663 structure.mLineBounds = parent->GetRect();
1664 return structure;
1667 MOZ_ASSERT_UNREACHABLE("cellmap is lying");
1668 return Err(NS_ERROR_FAILURE);
1671 int32_t nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame,
1672 int32_t aStartLine) {
1673 NS_ENSURE_TRUE(aFrame, -1);
1675 nsTableRowFrame* rowFrame = do_QueryFrame(aFrame);
1676 if (MOZ_UNLIKELY(!rowFrame)) {
1677 // When we do not have valid table structure in the DOM tree, somebody wants
1678 // to check the line number with an out-of-flow child of this frame because
1679 // its parent frame is set to this frame. Otherwise, the caller must have
1680 // a bug.
1681 MOZ_ASSERT(aFrame->GetParent() == this);
1682 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
1683 return -1;
1686 int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex();
1688 return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1;
1691 NS_IMETHODIMP
1692 nsTableRowGroupFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
1693 nsIFrame** aFirstVisual,
1694 nsIFrame** aLastVisual) {
1695 *aIsReordered = false;
1696 *aFirstVisual = nullptr;
1697 *aLastVisual = nullptr;
1698 return NS_OK;
1701 NS_IMETHODIMP
1702 nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
1703 nsIFrame** aFrameFound,
1704 bool* aPosIsBeforeFirstFrame,
1705 bool* aPosIsAfterLastFrame) {
1706 nsTableFrame* table = GetTableFrame();
1707 nsTableCellMap* cellMap = table->GetCellMap();
1709 *aFrameFound = nullptr;
1710 *aPosIsBeforeFirstFrame = true;
1711 *aPosIsAfterLastFrame = false;
1713 aLineNumber += GetStartRowIndex();
1714 int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber);
1715 if (numCells == 0) {
1716 return NS_OK;
1719 nsIFrame* frame = nullptr;
1720 int32_t colCount = table->GetColCount();
1721 for (int32_t i = 0; i < colCount; i++) {
1722 CellData* data = cellMap->GetDataAt(aLineNumber, i);
1723 if (data && data->IsOrig()) {
1724 frame = (nsIFrame*)data->GetCellFrame();
1725 break;
1728 NS_ASSERTION(frame, "cellmap is lying");
1729 bool isRTL = StyleDirection::Rtl == table->StyleVisibility()->mDirection;
1731 LineFrameFinder finder(aPos, table->GetSize(), table->GetWritingMode(),
1732 isRTL);
1734 int32_t n = numCells;
1735 while (n--) {
1736 finder.Scan(frame);
1737 if (finder.IsDone()) {
1738 break;
1740 frame = frame->GetNextSibling();
1742 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
1743 return NS_OK;
1746 // end nsLineIterator methods
1748 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty,
1749 nsTableRowGroupFrame::FrameCursorData)
1751 void nsTableRowGroupFrame::ClearRowCursor() {
1752 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1753 return;
1756 RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
1757 RemoveProperty(RowCursorProperty());
1760 nsTableRowGroupFrame::FrameCursorData* nsTableRowGroupFrame::SetupRowCursor() {
1761 if (HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1762 // We already have a valid row cursor. Don't waste time rebuilding it.
1763 return nullptr;
1766 nsIFrame* f = mFrames.FirstChild();
1767 int32_t count;
1768 for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) {
1769 f = f->GetNextSibling();
1771 if (!f) {
1772 // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother
1773 return nullptr;
1776 FrameCursorData* data = new FrameCursorData();
1777 SetProperty(RowCursorProperty(), data);
1778 AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
1779 return data;
1782 nsIFrame* nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY,
1783 nscoord* aOverflowAbove) {
1784 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1785 return nullptr;
1788 FrameCursorData* property = GetProperty(RowCursorProperty());
1789 uint32_t cursorIndex = property->mCursorIndex;
1790 uint32_t frameCount = property->mFrames.Length();
1791 if (cursorIndex >= frameCount) {
1792 return nullptr;
1794 nsIFrame* cursorFrame = property->mFrames[cursorIndex];
1796 // The cursor's frame list excludes frames with empty overflow-area, so
1797 // we don't need to check that here.
1799 // We use property->mOverflowBelow here instead of computing the frame's
1800 // true overflowArea.YMost(), because it is essential for the thresholds
1801 // to form a monotonically increasing sequence. Otherwise we would break
1802 // encountering a row whose overflowArea.YMost() is <= aY but which has
1803 // a row above it containing cell(s) that span to include aY.
1804 while (cursorIndex > 0 &&
1805 cursorFrame->GetRect().YMost() + property->mOverflowBelow > aY) {
1806 --cursorIndex;
1807 cursorFrame = property->mFrames[cursorIndex];
1809 while (cursorIndex + 1 < frameCount &&
1810 cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) {
1811 ++cursorIndex;
1812 cursorFrame = property->mFrames[cursorIndex];
1815 property->mCursorIndex = cursorIndex;
1816 *aOverflowAbove = property->mOverflowAbove;
1817 return cursorFrame;
1820 bool nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) {
1821 // The cursor requires a monotonically increasing sequence in order to
1822 // identify which rows can be skipped, and position:relative can move
1823 // rows around such that the overflow areas don't provide this.
1824 // We take the union of the overflow rect, and the frame's 'normal' position
1825 // (excluding position:relative changes) and record the max difference between
1826 // this combined overflow and the frame's rect.
1827 nsRect positionedOverflowRect = aFrame->InkOverflowRect();
1828 nsPoint positionedToNormal =
1829 aFrame->GetNormalPosition() - aFrame->GetPosition();
1830 nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal;
1832 nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect);
1833 if (overflowRect.IsEmpty()) {
1834 return true;
1836 nscoord overflowAbove = -overflowRect.y;
1837 nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height;
1838 mOverflowAbove = std::max(mOverflowAbove, overflowAbove);
1839 mOverflowBelow = std::max(mOverflowBelow, overflowBelow);
1840 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1841 // pretended earlier, or change the return type to void.
1842 mFrames.AppendElement(aFrame);
1843 return true;
1846 void nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey,
1847 bool aRebuildDisplayItems) {
1848 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
1849 if (GetTableFrame()->IsBorderCollapse()) {
1850 const bool rebuild = StaticPrefs::layout_display_list_retain_sc();
1851 GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
1852 aDisplayItemKey, rebuild);
1856 void nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect,
1857 uint32_t aDisplayItemKey,
1858 bool aRebuildDisplayItems) {
1859 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
1860 aRebuildDisplayItems);
1861 // If we have filters applied that would affects our bounds, then
1862 // we get an inactive layer created and this is computed
1863 // within FrameLayerBuilder
1864 GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey,
1865 aRebuildDisplayItems);