Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / layout / tables / nsTableRowGroupFrame.cpp
blobf8dac6138625a96154e82a8e00e3c6d7ca6af533
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();
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 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1328 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1330 // Row geometry may be going to change so we need to invalidate any row
1331 // cursor.
1332 ClearRowCursor();
1334 // see if a special bsize reflow needs to occur due to having a pct bsize
1335 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
1337 nsTableFrame* tableFrame = GetTableFrame();
1338 TableRowGroupReflowInput state(aReflowInput);
1339 const nsStyleVisibility* groupVis = StyleVisibility();
1340 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
1341 if (collapseGroup) {
1342 tableFrame->SetNeedToCollapse(true);
1345 // Check for an overflow list
1346 MoveOverflowToChildList();
1348 // Reflow the existing frames.
1349 bool splitDueToPageBreak = false;
1350 ReflowChildren(aPresContext, aDesiredSize, state, aStatus,
1351 &splitDueToPageBreak);
1353 // See if all the frames fit. Do not try to split anything if we're
1354 // not paginated ... we can't split across columns yet.
1355 WritingMode wm = aReflowInput.GetWritingMode();
1356 if (aReflowInput.mFlags.mTableIsSplittable &&
1357 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
1358 (aStatus.IsIncomplete() || splitDueToPageBreak ||
1359 aDesiredSize.BSize(wm) > aReflowInput.AvailableBSize())) {
1360 // Nope, find a place to split the row group
1361 auto& mutableRIFlags = const_cast<ReflowInput::Flags&>(aReflowInput.mFlags);
1362 const bool savedSpecialBSizeReflow = mutableRIFlags.mSpecialBSizeReflow;
1363 mutableRIFlags.mSpecialBSizeReflow = false;
1365 SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus,
1366 splitDueToPageBreak);
1368 mutableRIFlags.mSpecialBSizeReflow = savedSpecialBSizeReflow;
1371 // XXXmats The following is just bogus. We leave it here for now because
1372 // ReflowChildren should pull up rows from our next-in-flow before returning
1373 // a Complete status, but doesn't (bug 804888).
1374 if (GetNextInFlow() && GetNextInFlow()->PrincipalChildList().FirstChild()) {
1375 aStatus.SetIncomplete();
1378 SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) &&
1379 (aReflowInput.ComputedBSize() > 0));
1381 // Just set our isize to what was available.
1382 // The table will calculate the isize and not use our value.
1383 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
1385 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
1387 // If our parent is in initial reflow, it'll handle invalidating our
1388 // entire overflow rect.
1389 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
1390 aDesiredSize.Size(wm) != GetLogicalSize(wm)) {
1391 InvalidateFrame();
1394 FinishAndStoreOverflow(&aDesiredSize);
1396 // Any absolutely-positioned children will get reflowed in
1397 // nsIFrame::FixupPositionedTableParts in another pass, so propagate our
1398 // dirtiness to them before our parent clears our dirty bits.
1399 PushDirtyBitToAbsoluteFrames();
1402 bool nsTableRowGroupFrame::ComputeCustomOverflow(
1403 OverflowAreas& aOverflowAreas) {
1404 // Row cursor invariants depend on the ink overflow area of the rows,
1405 // which may have changed, so we need to clear the cursor now.
1406 ClearRowCursor();
1407 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
1410 /* virtual */
1411 void nsTableRowGroupFrame::DidSetComputedStyle(
1412 ComputedStyle* aOldComputedStyle) {
1413 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
1414 nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle);
1416 if (!aOldComputedStyle) {
1417 return; // avoid the following on init
1420 nsTableFrame* tableFrame = GetTableFrame();
1421 if (tableFrame->IsBorderCollapse() &&
1422 tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) {
1423 TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(),
1424 GetRowCount());
1425 tableFrame->AddBCDamageArea(damageArea);
1429 void nsTableRowGroupFrame::AppendFrames(ChildListID aListID,
1430 nsFrameList&& aFrameList) {
1431 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1433 DrainSelfOverflowList(); // ensure the last frame is in mFrames
1434 ClearRowCursor();
1436 // collect the new row frames in an array
1437 // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
1438 AutoTArray<nsTableRowFrame*, 8> rows;
1439 for (nsIFrame* f : aFrameList) {
1440 nsTableRowFrame* rowFrame = do_QueryFrame(f);
1441 NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
1442 if (rowFrame) {
1443 NS_ASSERTION(
1444 mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay,
1445 "wrong display type on rowframe");
1446 rows.AppendElement(rowFrame);
1450 int32_t rowIndex = GetRowCount();
1451 // Append the frames to the sibling chain
1452 mFrames.AppendFrames(nullptr, std::move(aFrameList));
1454 if (rows.Length() > 0) {
1455 nsTableFrame* tableFrame = GetTableFrame();
1456 tableFrame->AppendRows(this, rowIndex, rows);
1457 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1458 NS_FRAME_HAS_DIRTY_CHILDREN);
1459 tableFrame->SetGeometryDirty();
1463 void nsTableRowGroupFrame::InsertFrames(
1464 ChildListID aListID, nsIFrame* aPrevFrame,
1465 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
1466 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1467 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
1468 "inserting after sibling frame with different parent");
1470 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
1471 ClearRowCursor();
1473 // collect the new row frames in an array
1474 // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
1475 nsTableFrame* tableFrame = GetTableFrame();
1476 nsTArray<nsTableRowFrame*> rows;
1477 bool gotFirstRow = false;
1478 for (nsIFrame* f : aFrameList) {
1479 nsTableRowFrame* rowFrame = do_QueryFrame(f);
1480 NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
1481 if (rowFrame) {
1482 NS_ASSERTION(
1483 mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay,
1484 "wrong display type on rowframe");
1485 rows.AppendElement(rowFrame);
1486 if (!gotFirstRow) {
1487 rowFrame->SetFirstInserted(true);
1488 gotFirstRow = true;
1489 tableFrame->SetRowInserted(true);
1494 int32_t startRowIndex = GetStartRowIndex();
1495 // Insert the frames in the sibling chain
1496 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
1498 int32_t numRows = rows.Length();
1499 if (numRows > 0) {
1500 nsTableRowFrame* prevRow =
1501 (nsTableRowFrame*)nsTableFrame::GetFrameAtOrBefore(
1502 this, aPrevFrame, LayoutFrameType::TableRow);
1503 int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex;
1504 tableFrame->InsertRows(this, rows, rowIndex, true);
1506 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1507 NS_FRAME_HAS_DIRTY_CHILDREN);
1508 tableFrame->SetGeometryDirty();
1512 void nsTableRowGroupFrame::RemoveFrame(DestroyContext& aContext,
1513 ChildListID aListID,
1514 nsIFrame* aOldFrame) {
1515 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
1517 ClearRowCursor();
1519 // XXX why are we doing the QI stuff? There shouldn't be any non-rows here.
1520 nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame);
1521 if (rowFrame) {
1522 nsTableFrame* tableFrame = GetTableFrame();
1523 // remove the rows from the table (and flag a rebalance)
1524 tableFrame->RemoveRows(*rowFrame, 1, true);
1526 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1527 NS_FRAME_HAS_DIRTY_CHILDREN);
1528 tableFrame->SetGeometryDirty();
1530 mFrames.DestroyFrame(aContext, aOldFrame);
1533 /* virtual */
1534 nsMargin nsTableRowGroupFrame::GetUsedMargin() const {
1535 return nsMargin(0, 0, 0, 0);
1538 /* virtual */
1539 nsMargin nsTableRowGroupFrame::GetUsedBorder() const {
1540 return nsMargin(0, 0, 0, 0);
1543 /* virtual */
1544 nsMargin nsTableRowGroupFrame::GetUsedPadding() const {
1545 return nsMargin(0, 0, 0, 0);
1548 nscoord nsTableRowGroupFrame::GetBSizeBasis(const ReflowInput& aReflowInput) {
1549 nscoord result = 0;
1550 nsTableFrame* tableFrame = GetTableFrame();
1551 int32_t startRowIndex = GetStartRowIndex();
1552 if ((aReflowInput.ComputedBSize() > 0) &&
1553 (aReflowInput.ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
1554 nscoord cellSpacing = tableFrame->GetRowSpacing(
1555 startRowIndex,
1556 std::max(startRowIndex, startRowIndex + GetRowCount() - 1));
1557 result = aReflowInput.ComputedBSize() - cellSpacing;
1558 } else {
1559 const ReflowInput* parentRI = aReflowInput.mParentReflowInput;
1560 if (parentRI && (tableFrame != parentRI->mFrame)) {
1561 parentRI = parentRI->mParentReflowInput;
1563 if (parentRI && (tableFrame == parentRI->mFrame) &&
1564 (parentRI->ComputedBSize() > 0) &&
1565 (parentRI->ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
1566 nscoord cellSpacing =
1567 tableFrame->GetRowSpacing(-1, tableFrame->GetRowCount());
1568 result = parentRI->ComputedBSize() - cellSpacing;
1572 return result;
1575 bool nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame,
1576 nsTableRowFrame* aRowFrame) {
1577 int32_t rowIndex = aRowFrame->GetRowIndex();
1579 // It's a simple row frame if there are no cells that span into or
1580 // across the row
1581 int32_t numEffCols = aTableFrame->GetEffectiveColCount();
1582 if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) &&
1583 !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) {
1584 return true;
1587 return false;
1590 /** find page break before the first row **/
1591 bool nsTableRowGroupFrame::HasInternalBreakBefore() const {
1592 nsIFrame* firstChild = mFrames.FirstChild();
1593 if (!firstChild) return false;
1594 return firstChild->StyleDisplay()->BreakBefore();
1597 /** find page break after the last row **/
1598 bool nsTableRowGroupFrame::HasInternalBreakAfter() const {
1599 nsIFrame* lastChild = mFrames.LastChild();
1600 if (!lastChild) 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) = PresContext()->DevPixelsToAppUnits(
1631 firstRowFrame->GetBStartBCBorderWidth());
1632 border.BEnd(aWM) =
1633 PresContext()->DevPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth());
1634 return border;
1637 // nsILineIterator methods
1638 int32_t nsTableRowGroupFrame::GetNumLines() const { return GetRowCount(); }
1640 bool nsTableRowGroupFrame::IsLineIteratorFlowRTL() {
1641 return StyleDirection::Rtl == GetTableFrame()->StyleVisibility()->mDirection;
1644 Result<nsILineIterator::LineInfo, nsresult> nsTableRowGroupFrame::GetLine(
1645 int32_t aLineNumber) {
1646 if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) {
1647 return Err(NS_ERROR_FAILURE);
1649 LineInfo structure;
1650 nsTableFrame* table = GetTableFrame();
1651 nsTableCellMap* cellMap = table->GetCellMap();
1652 aLineNumber += GetStartRowIndex();
1654 structure.mNumFramesOnLine =
1655 cellMap->GetNumCellsOriginatingInRow(aLineNumber);
1656 if (structure.mNumFramesOnLine == 0) {
1657 return structure;
1659 int32_t colCount = table->GetColCount();
1660 for (int32_t i = 0; i < colCount; i++) {
1661 CellData* data = cellMap->GetDataAt(aLineNumber, i);
1662 if (data && data->IsOrig()) {
1663 structure.mFirstFrameOnLine = (nsIFrame*)data->GetCellFrame();
1664 nsIFrame* parent = structure.mFirstFrameOnLine->GetParent();
1665 structure.mLineBounds = parent->GetRect();
1666 return structure;
1669 MOZ_ASSERT_UNREACHABLE("cellmap is lying");
1670 return Err(NS_ERROR_FAILURE);
1673 int32_t nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame,
1674 int32_t aStartLine) {
1675 NS_ENSURE_TRUE(aFrame, -1);
1677 nsTableRowFrame* rowFrame = do_QueryFrame(aFrame);
1678 if (MOZ_UNLIKELY(!rowFrame)) {
1679 // When we do not have valid table structure in the DOM tree, somebody wants
1680 // to check the line number with an out-of-flow child of this frame because
1681 // its parent frame is set to this frame. Otherwise, the caller must have
1682 // a bug.
1683 MOZ_ASSERT(aFrame->GetParent() == this);
1684 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
1685 return -1;
1688 int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex();
1690 return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1;
1693 NS_IMETHODIMP
1694 nsTableRowGroupFrame::CheckLineOrder(int32_t aLine, bool* aIsReordered,
1695 nsIFrame** aFirstVisual,
1696 nsIFrame** aLastVisual) {
1697 *aIsReordered = false;
1698 *aFirstVisual = nullptr;
1699 *aLastVisual = nullptr;
1700 return NS_OK;
1703 NS_IMETHODIMP
1704 nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber, nsPoint aPos,
1705 nsIFrame** aFrameFound,
1706 bool* aPosIsBeforeFirstFrame,
1707 bool* aPosIsAfterLastFrame) {
1708 nsTableFrame* table = GetTableFrame();
1709 nsTableCellMap* cellMap = table->GetCellMap();
1711 *aFrameFound = nullptr;
1712 *aPosIsBeforeFirstFrame = true;
1713 *aPosIsAfterLastFrame = false;
1715 aLineNumber += GetStartRowIndex();
1716 int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber);
1717 if (numCells == 0) {
1718 return NS_OK;
1721 nsIFrame* frame = nullptr;
1722 int32_t colCount = table->GetColCount();
1723 for (int32_t i = 0; i < colCount; i++) {
1724 CellData* data = cellMap->GetDataAt(aLineNumber, i);
1725 if (data && data->IsOrig()) {
1726 frame = (nsIFrame*)data->GetCellFrame();
1727 break;
1730 NS_ASSERTION(frame, "cellmap is lying");
1731 bool isRTL = StyleDirection::Rtl == table->StyleVisibility()->mDirection;
1733 LineFrameFinder finder(aPos, table->GetSize(), table->GetWritingMode(),
1734 isRTL);
1736 int32_t n = numCells;
1737 while (n--) {
1738 finder.Scan(frame);
1739 if (finder.IsDone()) {
1740 break;
1742 frame = frame->GetNextSibling();
1744 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame);
1745 return NS_OK;
1748 // end nsLineIterator methods
1750 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty,
1751 nsTableRowGroupFrame::FrameCursorData)
1753 void nsTableRowGroupFrame::ClearRowCursor() {
1754 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1755 return;
1758 RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
1759 RemoveProperty(RowCursorProperty());
1762 nsTableRowGroupFrame::FrameCursorData* nsTableRowGroupFrame::SetupRowCursor() {
1763 if (HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1764 // We already have a valid row cursor. Don't waste time rebuilding it.
1765 return nullptr;
1768 nsIFrame* f = mFrames.FirstChild();
1769 int32_t count;
1770 for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) {
1771 f = f->GetNextSibling();
1773 if (!f) {
1774 // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother
1775 return nullptr;
1778 FrameCursorData* data = new FrameCursorData();
1779 SetProperty(RowCursorProperty(), data);
1780 AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
1781 return data;
1784 nsIFrame* nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY,
1785 nscoord* aOverflowAbove) {
1786 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
1787 return nullptr;
1790 FrameCursorData* property = GetProperty(RowCursorProperty());
1791 uint32_t cursorIndex = property->mCursorIndex;
1792 uint32_t frameCount = property->mFrames.Length();
1793 if (cursorIndex >= frameCount) 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()) return true;
1834 nscoord overflowAbove = -overflowRect.y;
1835 nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height;
1836 mOverflowAbove = std::max(mOverflowAbove, overflowAbove);
1837 mOverflowBelow = std::max(mOverflowBelow, overflowBelow);
1838 // XXX(Bug 1631371) Check if this should use a fallible operation as it
1839 // pretended earlier, or change the return type to void.
1840 mFrames.AppendElement(aFrame);
1841 return true;
1844 void nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey,
1845 bool aRebuildDisplayItems) {
1846 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
1847 if (GetTableFrame()->IsBorderCollapse()) {
1848 const bool rebuild = StaticPrefs::layout_display_list_retain_sc();
1849 GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
1850 aDisplayItemKey, rebuild);
1854 void nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect,
1855 uint32_t aDisplayItemKey,
1856 bool aRebuildDisplayItems) {
1857 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
1858 aRebuildDisplayItems);
1859 // If we have filters applied that would affects our bounds, then
1860 // we get an inactive layer created and this is computed
1861 // within FrameLayerBuilder
1862 GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey,
1863 aRebuildDisplayItems);