nsTableRowGroupFrame.cpp (74651B)
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" 6 7 #include <algorithm> 8 9 #include "mozilla/ComputedStyle.h" 10 #include "mozilla/PresShell.h" 11 #include "mozilla/StaticPrefs_layout.h" 12 #include "nsCOMPtr.h" 13 #include "nsCSSFrameConstructor.h" 14 #include "nsCSSRendering.h" 15 #include "nsCellMap.h" //table cell navigation 16 #include "nsDisplayList.h" 17 #include "nsGkAtoms.h" 18 #include "nsHTMLParts.h" 19 #include "nsIContent.h" 20 #include "nsIFrame.h" 21 #include "nsIFrameInlines.h" 22 #include "nsPresContext.h" 23 #include "nsStyleConsts.h" 24 #include "nsTableCellFrame.h" 25 #include "nsTableFrame.h" 26 #include "nsTableRowFrame.h" 27 28 using namespace mozilla; 29 using namespace mozilla::layout; 30 31 namespace mozilla { 32 33 struct TableRowGroupReflowInput final { 34 // Our reflow input 35 const ReflowInput& mReflowInput; 36 37 // The available size (computed from the parent) 38 LogicalSize mAvailSize; 39 40 // Running block-offset 41 nscoord mBCoord = 0; 42 43 explicit TableRowGroupReflowInput(const ReflowInput& aReflowInput) 44 : mReflowInput(aReflowInput), mAvailSize(aReflowInput.AvailableSize()) {} 45 46 ~TableRowGroupReflowInput() = default; 47 }; 48 49 } // namespace mozilla 50 51 nsTableRowGroupFrame::nsTableRowGroupFrame(ComputedStyle* aStyle, 52 nsPresContext* aPresContext) 53 : nsContainerFrame(aStyle, aPresContext, kClassID) { 54 SetRepeatable(false); 55 } 56 57 nsTableRowGroupFrame::~nsTableRowGroupFrame() = default; 58 59 void nsTableRowGroupFrame::Destroy(DestroyContext& aContext) { 60 nsTableFrame::MaybeUnregisterPositionedTablePart(this); 61 nsContainerFrame::Destroy(aContext); 62 } 63 64 NS_QUERYFRAME_HEAD(nsTableRowGroupFrame) 65 NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame) 66 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 67 68 int32_t nsTableRowGroupFrame::GetRowCount() const { 69 #ifdef DEBUG 70 for (nsIFrame* f : mFrames) { 71 NS_ASSERTION(f->StyleDisplay()->mDisplay == mozilla::StyleDisplay::TableRow, 72 "Unexpected display"); 73 NS_ASSERTION(f->IsTableRowFrame(), "Unexpected frame type"); 74 } 75 #endif 76 77 return mFrames.GetLength(); 78 } 79 80 int32_t nsTableRowGroupFrame::GetStartRowIndex() const { 81 int32_t result = -1; 82 if (mFrames.NotEmpty()) { 83 NS_ASSERTION(mFrames.FirstChild()->IsTableRowFrame(), 84 "Unexpected frame type"); 85 result = static_cast<nsTableRowFrame*>(mFrames.FirstChild())->GetRowIndex(); 86 } 87 // if the row group doesn't have any children, get it the hard way 88 if (-1 == result) { 89 return GetTableFrame()->GetStartRowIndex(this); 90 } 91 92 return result; 93 } 94 95 void nsTableRowGroupFrame::AdjustRowIndices(int32_t aRowIndex, 96 int32_t anAdjustment) { 97 for (nsIFrame* rowFrame : mFrames) { 98 if (mozilla::StyleDisplay::TableRow == rowFrame->StyleDisplay()->mDisplay) { 99 int32_t index = ((nsTableRowFrame*)rowFrame)->GetRowIndex(); 100 if (index >= aRowIndex) { 101 ((nsTableRowFrame*)rowFrame)->SetRowIndex(index + anAdjustment); 102 } 103 } 104 } 105 } 106 107 int32_t nsTableRowGroupFrame::GetAdjustmentForStoredIndex( 108 int32_t aStoredIndex) { 109 nsTableFrame* tableFrame = GetTableFrame(); 110 return tableFrame->GetAdjustmentForStoredIndex(aStoredIndex); 111 } 112 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; 132 } 133 currentRowFrame = do_QueryFrame(currentRowFrame->GetNextSibling()); 134 if (!currentRowFrame) { 135 MOZ_ASSERT_UNREACHABLE("expected another row frame"); 136 break; 137 } 138 } 139 } 140 141 void nsTableRowGroupFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) { 142 nsTableFrame* tableFrame = GetTableFrame(); 143 return tableFrame->AddDeletedRowIndex(aDeletedRowStoredIndex); 144 } 145 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); 155 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); 165 166 // Move to the next cell frame 167 copyCellFrame = copyCellFrame->GetNextCell(); 168 originalCellFrame = originalCellFrame->GetNextCell(); 169 } 170 171 // Move to the next row frame 172 originalRowFrame = originalRowFrame->GetNextRow(); 173 copyRowFrame = copyRowFrame->GetNextRow(); 174 } 175 } 176 177 // Handle the child-traversal part of DisplayGenericTablePart 178 static void DisplayRows(nsDisplayListBuilder* aBuilder, 179 nsTableRowGroupFrame* aFrame, 180 const nsDisplayListSet& aLists) { 181 if (aFrame->HidesContent()) { 182 return; 183 } 184 nscoord overflowAbove; 185 // Don't try to use the row cursor if we have to descend into placeholders; 186 // we might have rows containing placeholders, where the row's overflow 187 // area doesn't intersect the dirty rect but we need to descend into the row 188 // to see out of flows. 189 // Note that we really want to check ShouldDescendIntoFrame for all 190 // the rows in |f|, but that's exactly what we're trying to avoid, so we 191 // approximate it by checking it for |f|: if it's true for any row 192 // in |f| then it's true for |f| itself. 193 nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(aFrame, true) 194 ? nullptr 195 : aFrame->GetFirstRowContaining( 196 aBuilder->GetVisibleRect().y, &overflowAbove); 197 198 if (kid) { 199 // have a cursor, use it 200 while (kid) { 201 if (kid->GetRect().y - overflowAbove >= 202 aBuilder->GetVisibleRect().YMost()) { 203 break; 204 } 205 aFrame->BuildDisplayListForChild(aBuilder, kid, aLists); 206 kid = kid->GetNextSibling(); 207 } 208 return; 209 } 210 211 // No cursor. Traverse children the hard way and build a cursor while we're at 212 // it 213 nsTableRowGroupFrame::FrameCursorData* cursor = aFrame->SetupRowCursor(); 214 kid = aFrame->PrincipalChildList().FirstChild(); 215 while (kid) { 216 aFrame->BuildDisplayListForChild(aBuilder, kid, aLists); 217 218 if (cursor) { 219 if (!cursor->AppendFrame(kid)) { 220 aFrame->ClearRowCursor(); 221 return; 222 } 223 } 224 225 kid = kid->GetNextSibling(); 226 } 227 if (cursor) { 228 cursor->FinishBuildingCursor(); 229 } 230 } 231 232 void nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 233 const nsDisplayListSet& aLists) { 234 DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground()); 235 236 for (nsTableRowFrame* row = GetFirstRow(); row; row = row->GetNextRow()) { 237 if (!aBuilder->GetDirtyRect().Intersects(row->InkOverflowRect() + 238 row->GetNormalPosition())) { 239 continue; 240 } 241 row->PaintCellBackgroundsForFrame(this, aBuilder, aLists, 242 row->GetNormalPosition()); 243 } 244 245 DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground()); 246 247 DisplayOutline(aBuilder, aLists); 248 249 DisplayRows(aBuilder, this, aLists); 250 } 251 252 LogicalSides nsTableRowGroupFrame::GetLogicalSkipSides() const { 253 LogicalSides skip(mWritingMode); 254 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == 255 StyleBoxDecorationBreak::Clone)) { 256 return skip; 257 } 258 259 if (GetPrevInFlow()) { 260 skip += LogicalSide::BStart; 261 } 262 if (GetNextInFlow()) { 263 skip += LogicalSide::BEnd; 264 } 265 return skip; 266 } 267 268 // Position and size aKidFrame and update our reflow input. 269 void nsTableRowGroupFrame::PlaceChild( 270 nsPresContext* aPresContext, TableRowGroupReflowInput& aReflowInput, 271 nsIFrame* aKidFrame, const ReflowInput& aKidReflowInput, WritingMode aWM, 272 const LogicalPoint& aKidPosition, const nsSize& aContainerSize, 273 ReflowOutput& aDesiredSize, const nsRect& aOriginalKidRect, 274 const nsRect& aOriginalKidInkOverflow) { 275 bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 276 277 // Place and size the child 278 FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, &aKidReflowInput, 279 aWM, aKidPosition, aContainerSize, 280 ReflowChildFlags::ApplyRelativePositioning); 281 282 nsTableFrame* tableFrame = GetTableFrame(); 283 if (tableFrame->IsBorderCollapse()) { 284 nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, 285 aOriginalKidInkOverflow, isFirstReflow); 286 } 287 288 // Adjust the running block-offset 289 aReflowInput.mBCoord += aDesiredSize.BSize(aWM); 290 291 // If our block-size is constrained then update the available bsize 292 if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(aWM)) { 293 aReflowInput.mAvailSize.BSize(aWM) -= aDesiredSize.BSize(aWM); 294 } 295 } 296 297 void nsTableRowGroupFrame::InitChildReflowInput(nsPresContext* aPresContext, 298 bool aBorderCollapse, 299 ReflowInput& aReflowInput) { 300 const auto childWM = aReflowInput.GetWritingMode(); 301 LogicalMargin border(childWM); 302 if (aBorderCollapse) { 303 auto* rowFrame = static_cast<nsTableRowFrame*>(aReflowInput.mFrame); 304 border = rowFrame->GetBCBorderWidth(childWM); 305 } 306 const LogicalMargin zeroPadding(childWM); 307 aReflowInput.Init(aPresContext, Nothing(), Some(border), Some(zeroPadding)); 308 } 309 310 static void CacheRowBSizesForPrinting(nsTableRowFrame* aFirstRow, 311 WritingMode aWM) { 312 for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) { 313 if (!row->GetPrevInFlow()) { 314 row->SetUnpaginatedBSize(row->BSize(aWM)); 315 } 316 } 317 } 318 319 void nsTableRowGroupFrame::ReflowChildren( 320 nsPresContext* aPresContext, ReflowOutput& aDesiredSize, 321 TableRowGroupReflowInput& aReflowInput, nsReflowStatus& aStatus, 322 bool* aPageBreakBeforeEnd) { 323 if (aPageBreakBeforeEnd) { 324 *aPageBreakBeforeEnd = false; 325 } 326 327 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode(); 328 nsTableFrame* tableFrame = GetTableFrame(); 329 const bool borderCollapse = tableFrame->IsBorderCollapse(); 330 331 // XXXldb Should we really be checking IsPaginated(), 332 // or should we *only* check available block-size? 333 // (Think about multi-column layout!) 334 bool isPaginated = aPresContext->IsPaginated() && 335 NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm); 336 337 bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() || 338 tableFrame->IsGeometryDirty() || 339 tableFrame->NeedToCollapse(); 340 341 // in vertical-rl mode, we always need the row bsizes in order to 342 // get the necessary containerSize for placing our kids 343 bool needToCalcRowBSizes = reflowAllKids || wm.IsVerticalRL(); 344 345 nsSize containerSize = 346 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); 347 348 nsIFrame* prevKidFrame = nullptr; 349 for (nsTableRowFrame* kidFrame = GetFirstRow(); kidFrame; 350 prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextRow()) { 351 const nscoord rowSpacing = 352 tableFrame->GetRowSpacing(kidFrame->GetRowIndex()); 353 354 // Reflow the row frame 355 if (reflowAllKids || kidFrame->IsSubtreeDirty() || 356 (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow && 357 (isPaginated || 358 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { 359 LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize); 360 nsRect oldKidInkOverflow = kidFrame->InkOverflowRect(); 361 362 ReflowOutput kidDesiredSize(aReflowInput.mReflowInput); 363 364 // Reflow the child into the available space, giving it as much bsize as 365 // it wants. We'll deal with splitting later after we've computed the row 366 // bsizes, taking into account cells with row spans... 367 LogicalSize kidAvailSize = aReflowInput.mAvailSize; 368 kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 369 ReflowInput kidReflowInput(aPresContext, aReflowInput.mReflowInput, 370 kidFrame, kidAvailSize, Nothing(), 371 ReflowInput::InitFlag::CallerWillInit); 372 InitChildReflowInput(aPresContext, borderCollapse, kidReflowInput); 373 374 // This can indicate that columns were resized. 375 if (aReflowInput.mReflowInput.IsIResize()) { 376 kidReflowInput.SetIResize(true); 377 } 378 379 NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame, 380 "If we're not on the first frame, we should have a " 381 "previous sibling..."); 382 // If prev row has nonzero YMost, then we can't be at the top of the page 383 if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) { 384 kidReflowInput.mFlags.mIsTopOfPage = false; 385 } 386 387 LogicalPoint kidPosition(wm, 0, aReflowInput.mBCoord); 388 ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, wm, 389 kidPosition, containerSize, ReflowChildFlags::Default, 390 aStatus); 391 392 // Place the child 393 PlaceChild(aPresContext, aReflowInput, kidFrame, kidReflowInput, wm, 394 kidPosition, containerSize, kidDesiredSize, 395 oldKidRect.GetPhysicalRect(wm, containerSize), 396 oldKidInkOverflow); 397 aReflowInput.mBCoord += rowSpacing; 398 399 if (!reflowAllKids) { 400 if (IsSimpleRowFrame(tableFrame, kidFrame)) { 401 // Inform the row of its new bsize. 402 kidFrame->DidResize(); 403 // the overflow area may have changed inflate the overflow area 404 const nsStylePosition* stylePos = StylePosition(); 405 if (tableFrame->IsAutoBSize(wm) && 406 !stylePos->BSize(wm, AnchorPosResolutionParams::From(this)) 407 ->ConvertsToLength()) { 408 // Because other cells in the row may need to be aligned 409 // differently, repaint the entire row 410 InvalidateFrame(); 411 } else if (oldKidRect.BSize(wm) != kidDesiredSize.BSize(wm)) { 412 needToCalcRowBSizes = true; 413 } 414 } else { 415 needToCalcRowBSizes = true; 416 } 417 } 418 419 if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) { 420 nsTableRowFrame* nextRow = kidFrame->GetNextRow(); 421 if (nextRow) { 422 *aPageBreakBeforeEnd = 423 nsTableFrame::PageBreakAfter(kidFrame, nextRow); 424 } 425 } 426 } else { 427 // Move a child that was skipped during a reflow. 428 const LogicalPoint oldPosition = 429 kidFrame->GetLogicalNormalPosition(wm, containerSize); 430 if (oldPosition.B(wm) != aReflowInput.mBCoord) { 431 kidFrame->InvalidateFrameSubtree(); 432 const LogicalPoint offset(wm, 0, 433 aReflowInput.mBCoord - oldPosition.B(wm)); 434 kidFrame->MovePositionBy(wm, offset); 435 kidFrame->InvalidateFrameSubtree(); 436 } 437 438 // Adjust the running b-offset so we know where the next row should be 439 // placed 440 nscoord bSize = kidFrame->BSize(wm) + rowSpacing; 441 aReflowInput.mBCoord += bSize; 442 443 if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm)) { 444 aReflowInput.mAvailSize.BSize(wm) -= bSize; 445 } 446 } 447 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); 448 } 449 450 if (GetFirstRow()) { 451 aReflowInput.mBCoord -= 452 tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount()); 453 } 454 455 // Return our desired rect 456 aDesiredSize.ISize(wm) = aReflowInput.mReflowInput.AvailableISize(); 457 aDesiredSize.BSize(wm) = aReflowInput.mBCoord; 458 459 if (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow) { 460 DidResizeRows(aDesiredSize); 461 if (isPaginated) { 462 CacheRowBSizesForPrinting(GetFirstRow(), wm); 463 } 464 } else if (needToCalcRowBSizes) { 465 CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.mReflowInput); 466 if (!reflowAllKids) { 467 InvalidateFrame(); 468 } 469 } 470 } 471 472 nsTableRowFrame* nsTableRowGroupFrame::GetFirstRow() const { 473 nsIFrame* firstChild = mFrames.FirstChild(); 474 MOZ_ASSERT( 475 !firstChild || static_cast<nsTableRowFrame*>(do_QueryFrame(firstChild)), 476 "How do we have a non-row child?"); 477 return static_cast<nsTableRowFrame*>(firstChild); 478 } 479 480 nsTableRowFrame* nsTableRowGroupFrame::GetLastRow() const { 481 nsIFrame* lastChild = mFrames.LastChild(); 482 MOZ_ASSERT( 483 !lastChild || static_cast<nsTableRowFrame*>(do_QueryFrame(lastChild)), 484 "How do we have a non-row child?"); 485 return static_cast<nsTableRowFrame*>(lastChild); 486 } 487 488 struct RowInfo { 489 RowInfo() { bSize = pctBSize = hasStyleBSize = hasPctBSize = isSpecial = 0; } 490 unsigned bSize; // content bsize or fixed bsize, excluding pct bsize 491 unsigned pctBSize : 29; // pct bsize 492 unsigned hasStyleBSize : 1; 493 unsigned hasPctBSize : 1; 494 unsigned isSpecial : 1; // there is no cell originating in the row with 495 // rowspan=1 and there are at least 2 cells spanning 496 // the row and there is no style bsize on the row 497 }; 498 499 static void UpdateBSizes(RowInfo& aRowInfo, nscoord aAdditionalBSize, 500 nscoord& aTotal, nscoord& aUnconstrainedTotal) { 501 aRowInfo.bSize += aAdditionalBSize; 502 aTotal += aAdditionalBSize; 503 if (!aRowInfo.hasStyleBSize) { 504 aUnconstrainedTotal += aAdditionalBSize; 505 } 506 } 507 508 void nsTableRowGroupFrame::DidResizeRows(ReflowOutput& aDesiredSize) { 509 // Update the cells spanning rows with their new bsizes. 510 // This is the place where all of the cells in the row get set to the bsize 511 // of the row. 512 // Reset the overflow area. 513 aDesiredSize.mOverflowAreas.Clear(); 514 for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame; 515 rowFrame = rowFrame->GetNextRow()) { 516 rowFrame->DidResize(); 517 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame); 518 } 519 } 520 521 // This calculates the bsize of all the rows and takes into account 522 // style bsize on the row group, style bsizes on rows and cells, style bsizes on 523 // rowspans. Actual row bsizes will be adjusted later if the table has a style 524 // bsize. Even if rows don't change bsize, this method must be called to set the 525 // bsizes of each cell in the row to the bsize of its row. 526 void nsTableRowGroupFrame::CalculateRowBSizes(nsPresContext* aPresContext, 527 ReflowOutput& aDesiredSize, 528 const ReflowInput& aReflowInput) { 529 nsTableFrame* tableFrame = GetTableFrame(); 530 const bool isPaginated = aPresContext->IsPaginated(); 531 532 int32_t numEffCols = tableFrame->GetEffectiveColCount(); 533 534 int32_t startRowIndex = GetStartRowIndex(); 535 // find the row corresponding to the row index we just found 536 nsTableRowFrame* startRowFrame = GetFirstRow(); 537 538 if (!startRowFrame) { 539 return; 540 } 541 542 // The current row group block-size is the block-origin of the 1st row 543 // we are about to calculate a block-size for. 544 WritingMode wm = aReflowInput.GetWritingMode(); 545 nsSize containerSize; // actual value is unimportant as we're initially 546 // computing sizes, not physical positions 547 nscoord startRowGroupBSize = 548 startRowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm); 549 550 int32_t numRows = 551 GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex()); 552 // Collect the current bsize of each row. 553 if (numRows <= 0) { 554 return; 555 } 556 557 AutoTArray<RowInfo, 32> rowInfo; 558 // XXX(Bug 1631371) Check if this should use a fallible operation as it 559 // pretended earlier. 560 rowInfo.AppendElements(numRows); 561 562 bool hasRowSpanningCell = false; 563 nscoord bSizeOfRows = 0; 564 nscoord bSizeOfUnStyledRows = 0; 565 // Get the bsize of each row without considering rowspans. This will be the 566 // max of the largest desired bsize of each cell, the largest style bsize of 567 // each cell, the style bsize of the row. 568 nscoord pctBSizeBasis = GetBSizeBasis(aReflowInput); 569 int32_t 570 rowIndex; // the index in rowInfo, not among the rows in the row group 571 nsTableRowFrame* rowFrame; 572 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; 573 rowFrame = rowFrame->GetNextRow(), rowIndex++) { 574 nscoord nonPctBSize = rowFrame->GetContentBSize(); 575 if (isPaginated) { 576 nonPctBSize = std::max(nonPctBSize, rowFrame->BSize(wm)); 577 } 578 if (!rowFrame->GetPrevInFlow()) { 579 if (rowFrame->HasPctBSize()) { 580 rowInfo[rowIndex].hasPctBSize = true; 581 rowInfo[rowIndex].pctBSize = rowFrame->GetInitialBSize(pctBSizeBasis); 582 } 583 rowInfo[rowIndex].hasStyleBSize = rowFrame->HasStyleBSize(); 584 nonPctBSize = std::max(nonPctBSize, rowFrame->GetFixedBSize()); 585 } 586 UpdateBSizes(rowInfo[rowIndex], nonPctBSize, bSizeOfRows, 587 bSizeOfUnStyledRows); 588 589 if (!rowInfo[rowIndex].hasStyleBSize) { 590 if (isPaginated || 591 tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) { 592 rowInfo[rowIndex].isSpecial = true; 593 // iteratate the row's cell frames to see if any do not have rowspan > 1 594 nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); 595 while (cellFrame) { 596 int32_t rowSpan = tableFrame->GetEffectiveRowSpan( 597 rowIndex + startRowIndex, *cellFrame); 598 if (1 == rowSpan) { 599 rowInfo[rowIndex].isSpecial = false; 600 break; 601 } 602 cellFrame = cellFrame->GetNextCell(); 603 } 604 } 605 } 606 // See if a cell spans into the row. If so we'll have to do the next step 607 if (!hasRowSpanningCell) { 608 if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) { 609 hasRowSpanningCell = true; 610 } 611 } 612 } 613 614 if (hasRowSpanningCell) { 615 // Get the bsize of cells with rowspans and allocate any extra space to the 616 // rows they span iteratate the child frames and process the row frames 617 // among them 618 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; 619 rowFrame = rowFrame->GetNextRow(), rowIndex++) { 620 // See if the row has an originating cell with rowspan > 1. We cannot 621 // determine this for a row in a continued row group by calling 622 // RowHasSpanningCells, because the row's fif may not have any originating 623 // cells yet the row may have a continued cell which originates in it. 624 if (GetPrevInFlow() || tableFrame->RowHasSpanningCells( 625 startRowIndex + rowIndex, numEffCols)) { 626 nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); 627 // iteratate the row's cell frames 628 while (cellFrame) { 629 const nscoord rowSpacing = 630 tableFrame->GetRowSpacing(startRowIndex + rowIndex); 631 int32_t rowSpan = tableFrame->GetEffectiveRowSpan( 632 rowIndex + startRowIndex, *cellFrame); 633 if ((rowIndex + rowSpan) > numRows) { 634 // there might be rows pushed already to the nextInFlow 635 rowSpan = numRows - rowIndex; 636 } 637 if (rowSpan > 1) { // a cell with rowspan > 1, determine the bsize of 638 // the rows it spans 639 nscoord bsizeOfRowsSpanned = 0; 640 nscoord bsizeOfUnStyledRowsSpanned = 0; 641 nscoord numSpecialRowsSpanned = 0; 642 nscoord cellSpacingTotal = 0; 643 int32_t spanX; 644 for (spanX = 0; spanX < rowSpan; spanX++) { 645 bsizeOfRowsSpanned += rowInfo[rowIndex + spanX].bSize; 646 if (!rowInfo[rowIndex + spanX].hasStyleBSize) { 647 bsizeOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].bSize; 648 } 649 if (0 != spanX) { 650 cellSpacingTotal += rowSpacing; 651 } 652 if (rowInfo[rowIndex + spanX].isSpecial) { 653 numSpecialRowsSpanned++; 654 } 655 } 656 nscoord bsizeOfAreaSpanned = bsizeOfRowsSpanned + cellSpacingTotal; 657 // get the bsize of the cell 658 LogicalSize cellFrameSize = cellFrame->GetLogicalSize(wm); 659 LogicalSize cellDesSize = cellFrame->GetDesiredSize(); 660 cellDesSize.BSize(wm) = rowFrame->CalcCellActualBSize( 661 cellFrame, cellDesSize.BSize(wm), wm); 662 cellFrameSize.BSize(wm) = cellDesSize.BSize(wm); 663 664 if (bsizeOfAreaSpanned < cellFrameSize.BSize(wm)) { 665 // the cell's bsize is larger than the available space of the rows 666 // it spans so distribute the excess bsize to the rows affected 667 nscoord extra = cellFrameSize.BSize(wm) - bsizeOfAreaSpanned; 668 nscoord extraUsed = 0; 669 if (0 == numSpecialRowsSpanned) { 670 // NS_ASSERTION(bsizeOfRowsSpanned > 0, "invalid row span 671 // situation"); 672 bool haveUnStyledRowsSpanned = (bsizeOfUnStyledRowsSpanned > 0); 673 nscoord divisor = (haveUnStyledRowsSpanned) 674 ? bsizeOfUnStyledRowsSpanned 675 : bsizeOfRowsSpanned; 676 if (divisor > 0) { 677 for (spanX = rowSpan - 1; spanX >= 0; spanX--) { 678 if (!haveUnStyledRowsSpanned || 679 !rowInfo[rowIndex + spanX].hasStyleBSize) { 680 // The amount of additional space each row gets is 681 // proportional to its bsize 682 float percent = ((float)rowInfo[rowIndex + spanX].bSize) / 683 ((float)divisor); 684 685 // give rows their percentage, except for the first row 686 // which gets the remainder 687 nscoord extraForRow = 688 (0 == spanX) 689 ? extra - extraUsed 690 : NSToCoordRound(((float)(extra)) * percent); 691 extraForRow = std::min(extraForRow, extra - extraUsed); 692 // update the row bsize 693 UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, 694 bSizeOfRows, bSizeOfUnStyledRows); 695 extraUsed += extraForRow; 696 if (extraUsed >= extra) { 697 NS_ASSERTION((extraUsed == extra), 698 "invalid row bsize calculation"); 699 break; 700 } 701 } 702 } 703 } else { 704 // put everything in the last row 705 UpdateBSizes(rowInfo[rowIndex + rowSpan - 1], extra, 706 bSizeOfRows, bSizeOfUnStyledRows); 707 } 708 } else { 709 // give the extra to the special rows 710 nscoord numSpecialRowsAllocated = 0; 711 for (spanX = rowSpan - 1; spanX >= 0; spanX--) { 712 if (rowInfo[rowIndex + spanX].isSpecial) { 713 // The amount of additional space each degenerate row gets 714 // is proportional to the number of them 715 float percent = 1.0f / ((float)numSpecialRowsSpanned); 716 717 // give rows their percentage, except for the first row 718 // which gets the remainder 719 nscoord extraForRow = 720 (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated) 721 ? extra - extraUsed 722 : NSToCoordRound(((float)(extra)) * percent); 723 extraForRow = std::min(extraForRow, extra - extraUsed); 724 // update the row bsize 725 UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, 726 bSizeOfRows, bSizeOfUnStyledRows); 727 extraUsed += extraForRow; 728 if (extraUsed >= extra) { 729 NS_ASSERTION((extraUsed == extra), 730 "invalid row bsize calculation"); 731 break; 732 } 733 } 734 } 735 } 736 } 737 } // if (rowSpan > 1) 738 cellFrame = cellFrame->GetNextCell(); 739 } // while (cellFrame) 740 } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) { 741 } // while (rowFrame) 742 } 743 744 // pct bsize rows have already got their content bsizes. 745 // Give them their pct bsizes up to pctBSizeBasis 746 nscoord extra = pctBSizeBasis - bSizeOfRows; 747 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0); 748 rowFrame = rowFrame->GetNextRow(), rowIndex++) { 749 RowInfo& rInfo = rowInfo[rowIndex]; 750 if (rInfo.hasPctBSize) { 751 nscoord rowExtra = 752 (rInfo.pctBSize > rInfo.bSize) ? rInfo.pctBSize - rInfo.bSize : 0; 753 rowExtra = std::min(rowExtra, extra); 754 UpdateBSizes(rInfo, rowExtra, bSizeOfRows, bSizeOfUnStyledRows); 755 extra -= rowExtra; 756 } 757 } 758 759 bool styleBSizeAllocation = false; 760 nscoord rowGroupBSize = startRowGroupBSize + bSizeOfRows + 761 tableFrame->GetRowSpacing(0, numRows - 1); 762 // if we have a style bsize, allocate the extra bsize to unconstrained rows 763 if ((aReflowInput.ComputedBSize() > rowGroupBSize) && 764 (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize())) { 765 nscoord extraComputedBSize = aReflowInput.ComputedBSize() - rowGroupBSize; 766 nscoord extraUsed = 0; 767 bool haveUnStyledRows = (bSizeOfUnStyledRows > 0); 768 nscoord divisor = (haveUnStyledRows) ? bSizeOfUnStyledRows : bSizeOfRows; 769 if (divisor > 0) { 770 styleBSizeAllocation = true; 771 for (rowIndex = 0; rowIndex < numRows; rowIndex++) { 772 if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleBSize) { 773 // The amount of additional space each row gets is based on the 774 // percentage of space it occupies 775 float percent = ((float)rowInfo[rowIndex].bSize) / ((float)divisor); 776 // give rows their percentage, except for the last row which gets the 777 // remainder 778 nscoord extraForRow = 779 (numRows - 1 == rowIndex) 780 ? extraComputedBSize - extraUsed 781 : NSToCoordRound(((float)extraComputedBSize) * percent); 782 extraForRow = std::min(extraForRow, extraComputedBSize - extraUsed); 783 // update the row bsize 784 UpdateBSizes(rowInfo[rowIndex], extraForRow, bSizeOfRows, 785 bSizeOfUnStyledRows); 786 extraUsed += extraForRow; 787 if (extraUsed >= extraComputedBSize) { 788 NS_ASSERTION((extraUsed == extraComputedBSize), 789 "invalid row bsize calculation"); 790 break; 791 } 792 } 793 } 794 } 795 rowGroupBSize = aReflowInput.ComputedBSize(); 796 } 797 798 if (wm.IsVertical()) { 799 // we need the correct containerSize below for block positioning in 800 // vertical-rl writing mode 801 containerSize.width = rowGroupBSize; 802 } 803 804 nscoord bOrigin = startRowGroupBSize; 805 // update the rows with their (potentially) new bsizes 806 for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; 807 rowFrame = rowFrame->GetNextRow(), rowIndex++) { 808 nsRect rowBounds = rowFrame->GetRect(); 809 LogicalSize rowBoundsSize(wm, rowBounds.Size()); 810 nsRect rowInkOverflow = rowFrame->InkOverflowRect(); 811 nscoord deltaB = 812 bOrigin - rowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm); 813 814 nscoord rowBSize = 815 (rowInfo[rowIndex].bSize > 0) ? rowInfo[rowIndex].bSize : 0; 816 817 if (deltaB != 0 || (rowBSize != rowBoundsSize.BSize(wm))) { 818 // Resize/move the row to its final size and position 819 if (deltaB != 0) { 820 rowFrame->InvalidateFrameSubtree(); 821 } 822 823 rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, deltaB)); 824 rowFrame->SetSize(LogicalSize(wm, rowBoundsSize.ISize(wm), rowBSize)); 825 826 nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowInkOverflow, 827 false); 828 829 if (deltaB != 0) { 830 // XXXbz we don't need to update our overflow area? 831 } 832 } 833 bOrigin += rowBSize + tableFrame->GetRowSpacing(startRowIndex + rowIndex); 834 } 835 836 if (isPaginated && styleBSizeAllocation) { 837 // since the row group has a style bsize, cache the row bsizes, 838 // so next in flows can honor them 839 CacheRowBSizesForPrinting(GetFirstRow(), wm); 840 } 841 842 DidResizeRows(aDesiredSize); 843 844 aDesiredSize.BSize(wm) = rowGroupBSize; // Adjust our desired size 845 } 846 847 nscoord nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aBTotalOffset, 848 nscoord aISize, 849 WritingMode aWM) { 850 nsTableFrame* tableFrame = GetTableFrame(); 851 nsSize containerSize = tableFrame->GetSize(); 852 const nsStyleVisibility* groupVis = StyleVisibility(); 853 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; 854 if (collapseGroup) { 855 tableFrame->SetNeedToCollapse(true); 856 } 857 858 OverflowAreas overflow; 859 860 nsTableRowFrame* rowFrame = GetFirstRow(); 861 bool didCollapse = false; 862 nscoord bGroupOffset = 0; 863 while (rowFrame) { 864 bGroupOffset += rowFrame->CollapseRowIfNecessary( 865 bGroupOffset, aISize, collapseGroup, didCollapse); 866 ConsiderChildOverflow(overflow, rowFrame); 867 rowFrame = rowFrame->GetNextRow(); 868 } 869 870 LogicalRect groupRect = GetLogicalRect(aWM, containerSize); 871 nsRect oldGroupRect = GetRect(); 872 nsRect oldGroupInkOverflow = InkOverflowRect(); 873 874 groupRect.BSize(aWM) -= bGroupOffset; 875 if (didCollapse) { 876 // add back the cellspacing between rowgroups 877 groupRect.BSize(aWM) += 878 tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount()); 879 } 880 881 groupRect.BStart(aWM) -= aBTotalOffset; 882 groupRect.ISize(aWM) = aISize; 883 884 if (aBTotalOffset != 0) { 885 InvalidateFrameSubtree(); 886 } 887 888 SetRect(aWM, groupRect, containerSize); 889 overflow.UnionAllWith( 890 nsRect(0, 0, groupRect.Width(aWM), groupRect.Height(aWM))); 891 FinishAndStoreOverflow(overflow, groupRect.Size(aWM).GetPhysicalSize(aWM)); 892 nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupInkOverflow, 893 false); 894 895 return bGroupOffset; 896 } 897 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)); 903 904 // Add the continuing row frame to the child list. 905 mFrames.InsertFrame(nullptr, aRowFrame, contRowFrame); 906 907 // Push the continuing row frame and the frames that follow. 908 // This needs to match `UndoContinuedRow`. 909 PushChildrenToOverflow(contRowFrame, aRowFrame); 910 911 return contRowFrame; 912 } 913 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; 926 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; 953 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 967 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; 980 } 981 } 982 } else { 983 if (!aContRow) { 984 aContRow = CreateContinuingRowFrame(aLastRow); 985 } 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); 995 } 996 } 997 } 998 } 999 } 1000 } 1001 if (!haveRowSpan) { 1002 aDesiredBSize = aLastRow->GetLogicalNormalRect(wm, aContainerSize).BEnd(wm); 1003 } 1004 } 1005 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 1013 } 1014 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?"); 1020 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; 1028 } 1029 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); 1034 1035 // Put the overflow rows into our child list 1036 if (!overflows->IsEmpty()) { 1037 mFrames.InsertFrames(nullptr, rowBefore, std::move(*overflows)); 1038 } 1039 } 1040 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"); 1049 1050 const WritingMode wm = aReflowInput.GetWritingMode(); 1051 nsTableRowFrame* prevRowFrame = nullptr; 1052 aDesiredSize.BSize(wm) = 0; 1053 aDesiredSize.SetOverflowAreasToDesiredBounds(); 1054 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(); 1060 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"); 1065 1066 bool isTopOfPage = aReflowInput.mFlags.mIsTopOfPage; 1067 nsTableRowFrame* firstRowThisPage = GetFirstRow(); 1068 1069 // Need to dirty the table's geometry, or else the row might skip 1070 // reflowing its cell as an optimization. 1071 aTableFrame->SetGeometryDirty(); 1072 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)); 1095 1096 ReflowInput rowReflowInput( 1097 aPresContext, aReflowInput, rowFrame, 1098 availSize.ConvertTo(rowFrame->GetWritingMode(), wm), Nothing(), 1099 ReflowInput::InitFlag::CallerWillInit); 1100 1101 InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput); 1102 rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page 1103 ReflowOutput rowMetrics(aReflowInput); 1104 1105 // Get the old size before we reflow. 1106 nsRect oldRowRect = rowFrame->GetRect(); 1107 nsRect oldRowInkOverflow = rowFrame->InkOverflowRect(); 1108 1109 // Reflow the cell with the constrained bsize. A cell with rowspan >1 1110 // will get this reflow later during SplitSpanningCells. 1111 // 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); 1123 1124 if (!aRowForcedPageBreak && !aStatus.IsFullyComplete() && 1125 ShouldAvoidBreakInside(aReflowInput)) { 1126 aStatus.SetInlineLineBreakBeforeAndReset(); 1127 break; 1128 } 1129 1130 nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect, 1131 oldRowInkOverflow, false); 1132 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; 1149 } 1150 } else { 1151 // Put the row on the next page to give it more block-size. 1152 rowIsOnPage = false; 1153 } 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(); 1171 } 1172 aDesiredSize.BSize(wm) += rowMetrics.BSize(wm); 1173 if (prevRowFrame) { 1174 aDesiredSize.BSize(wm) += rowSpacing; 1175 } 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; 1183 } 1184 } 1185 } 1186 } else { 1187 // Put the row on the next page to give it more block-size. 1188 rowIsOnPage = false; 1189 } 1190 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; 1201 } 1202 spanningRowBEnd = 1203 prevRowFrame->GetLogicalNormalRect(wm, containerSize).BEnd(wm); 1204 lastRowThisPage = prevRowFrame; 1205 aStatus.Reset(); 1206 aStatus.SetIncomplete(); 1207 } 1208 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; 1229 } 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); 1236 1237 UndoContinuedRow(aPresContext, contRow); 1238 contRow = nullptr; 1239 nsTableRowFrame* oldLastRowThisPage = lastRowThisPage; 1240 lastRowThisPage = rowBefore; 1241 aStatus.Reset(); 1242 aStatus.SetIncomplete(); 1243 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; 1271 } 1272 } 1273 } 1274 } else { 1275 aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bMost); 1276 if (contRow) { 1277 aStatus.Reset(); 1278 aStatus.SetIncomplete(); 1279 } 1280 } 1281 if (aStatus.IsIncomplete() && !contRow) { 1282 if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) { 1283 PushChildrenToOverflow(nextRow, lastRowThisPage); 1284 } 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); 1294 } 1295 } 1296 break; 1297 } 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; 1307 } 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; 1311 } 1312 } 1313 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. 1317 */ 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!"); 1325 1326 // Row geometry may be going to change so we need to invalidate any row 1327 // cursor. 1328 ClearRowCursor(); 1329 1330 // see if a special bsize reflow needs to occur due to having a pct bsize 1331 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput); 1332 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); 1339 } 1340 1341 // Check for an overflow list 1342 MoveOverflowToChildList(); 1343 1344 // Reflow the existing frames. 1345 bool splitDueToPageBreak = false; 1346 ReflowChildren(aPresContext, aDesiredSize, state, aStatus, 1347 &splitDueToPageBreak); 1348 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; 1360 1361 SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus, 1362 splitDueToPageBreak); 1363 1364 mutableRIFlags.mSpecialBSizeReflow = savedSpecialBSizeReflow; 1365 } 1366 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(); 1372 } 1373 1374 SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) && 1375 (aReflowInput.ComputedBSize() > 0)); 1376 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(); 1380 1381 aDesiredSize.UnionOverflowAreasWithDesiredBounds(); 1382 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(); 1388 } 1389 1390 FinishAndStoreOverflow(&aDesiredSize); 1391 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(); 1396 } 1397 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); 1404 } 1405 1406 /* virtual */ 1407 void nsTableRowGroupFrame::DidSetComputedStyle( 1408 ComputedStyle* aOldComputedStyle) { 1409 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); 1410 nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle); 1411 1412 if (!aOldComputedStyle) { 1413 return; // avoid the following on init 1414 } 1415 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); 1422 } 1423 } 1424 1425 void nsTableRowGroupFrame::AppendFrames(ChildListID aListID, 1426 nsFrameList&& aFrameList) { 1427 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 1428 1429 DrainSelfOverflowList(); // ensure the last frame is in mFrames 1430 ClearRowCursor(); 1431 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); 1443 } 1444 } 1445 1446 int32_t rowIndex = GetRowCount(); 1447 // Append the frames to the sibling chain 1448 mFrames.AppendFrames(nullptr, std::move(aFrameList)); 1449 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(); 1456 } 1457 } 1458 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"); 1465 1466 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames 1467 ClearRowCursor(); 1468 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); 1486 } 1487 } 1488 } 1489 1490 int32_t startRowIndex = GetStartRowIndex(); 1491 // Insert the frames in the sibling chain 1492 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); 1493 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); 1501 1502 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 1503 NS_FRAME_HAS_DIRTY_CHILDREN); 1504 tableFrame->SetGeometryDirty(); 1505 } 1506 } 1507 1508 void nsTableRowGroupFrame::RemoveFrame(DestroyContext& aContext, 1509 ChildListID aListID, 1510 nsIFrame* aOldFrame) { 1511 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 1512 1513 ClearRowCursor(); 1514 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); 1521 1522 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 1523 NS_FRAME_HAS_DIRTY_CHILDREN); 1524 tableFrame->SetGeometryDirty(); 1525 } 1526 mFrames.DestroyFrame(aContext, aOldFrame); 1527 } 1528 1529 /* virtual */ 1530 nsMargin nsTableRowGroupFrame::GetUsedMargin() const { 1531 return nsMargin(0, 0, 0, 0); 1532 } 1533 1534 /* virtual */ 1535 nsMargin nsTableRowGroupFrame::GetUsedBorder() const { 1536 return nsMargin(0, 0, 0, 0); 1537 } 1538 1539 /* virtual */ 1540 nsMargin nsTableRowGroupFrame::GetUsedPadding() const { 1541 return nsMargin(0, 0, 0, 0); 1542 } 1543 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; 1558 } 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; 1565 } 1566 } 1567 1568 return result; 1569 } 1570 1571 bool nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame, 1572 nsTableRowFrame* aRowFrame) { 1573 int32_t rowIndex = aRowFrame->GetRowIndex(); 1574 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; 1581 } 1582 1583 return false; 1584 } 1585 1586 /** find page break before the first row **/ 1587 bool nsTableRowGroupFrame::HasInternalBreakBefore() const { 1588 nsIFrame* firstChild = mFrames.FirstChild(); 1589 if (!firstChild) { 1590 return false; 1591 } 1592 return firstChild->StyleDisplay()->BreakBefore(); 1593 } 1594 1595 /** find page break after the last row **/ 1596 bool nsTableRowGroupFrame::HasInternalBreakAfter() const { 1597 nsIFrame* lastChild = mFrames.LastChild(); 1598 if (!lastChild) { 1599 return false; 1600 } 1601 return lastChild->StyleDisplay()->BreakAfter(); 1602 } 1603 /* ----- global methods ----- */ 1604 1605 nsTableRowGroupFrame* NS_NewTableRowGroupFrame(PresShell* aPresShell, 1606 ComputedStyle* aStyle) { 1607 return new (aPresShell) 1608 nsTableRowGroupFrame(aStyle, aPresShell->GetPresContext()); 1609 } 1610 1611 NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) 1612 1613 #ifdef DEBUG_FRAME_DUMP 1614 nsresult nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const { 1615 return MakeFrameName(u"TableRowGroup"_ns, aResult); 1616 } 1617 #endif 1618 1619 LogicalMargin nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) { 1620 LogicalMargin border(aWM); 1621 nsTableRowFrame* firstRowFrame = GetFirstRow(); 1622 if (!firstRowFrame) { 1623 return border; 1624 } 1625 nsTableRowFrame* lastRowFrame = firstRowFrame; 1626 for (nsTableRowFrame* rowFrame = firstRowFrame->GetNextRow(); rowFrame; 1627 rowFrame = rowFrame->GetNextRow()) { 1628 lastRowFrame = rowFrame; 1629 } 1630 border.BStart(aWM) = firstRowFrame->GetBStartBCBorderWidth(); 1631 border.BEnd(aWM) = lastRowFrame->GetBEndBCBorderWidth(); 1632 return border; 1633 } 1634 1635 // nsILineIterator methods 1636 int32_t nsTableRowGroupFrame::GetNumLines() const { return GetRowCount(); } 1637 1638 bool nsTableRowGroupFrame::IsLineIteratorFlowRTL() { 1639 return StyleDirection::Rtl == GetTableFrame()->StyleVisibility()->mDirection; 1640 } 1641 1642 Result<nsILineIterator::LineInfo, nsresult> nsTableRowGroupFrame::GetLine( 1643 int32_t aLineNumber) { 1644 if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) { 1645 return Err(NS_ERROR_FAILURE); 1646 } 1647 LineInfo structure; 1648 nsTableFrame* table = GetTableFrame(); 1649 nsTableCellMap* cellMap = table->GetCellMap(); 1650 aLineNumber += GetStartRowIndex(); 1651 1652 structure.mNumFramesOnLine = 1653 cellMap->GetNumCellsOriginatingInRow(aLineNumber); 1654 if (structure.mNumFramesOnLine == 0) { 1655 return structure; 1656 } 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; 1665 } 1666 } 1667 MOZ_ASSERT_UNREACHABLE("cellmap is lying"); 1668 return Err(NS_ERROR_FAILURE); 1669 } 1670 1671 int32_t nsTableRowGroupFrame::FindLineContaining(const nsIFrame* aFrame, 1672 int32_t aStartLine) { 1673 NS_ENSURE_TRUE(aFrame, -1); 1674 1675 const 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; 1684 } 1685 1686 int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex(); 1687 1688 return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1; 1689 } 1690 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; 1699 } 1700 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(); 1708 1709 *aFrameFound = nullptr; 1710 *aPosIsBeforeFirstFrame = true; 1711 *aPosIsAfterLastFrame = false; 1712 1713 aLineNumber += GetStartRowIndex(); 1714 int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber); 1715 if (numCells == 0) { 1716 return NS_OK; 1717 } 1718 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; 1726 } 1727 } 1728 NS_ASSERTION(frame, "cellmap is lying"); 1729 bool isRTL = StyleDirection::Rtl == table->StyleVisibility()->mDirection; 1730 1731 LineFrameFinder finder(aPos, table->GetSize(), table->GetWritingMode(), 1732 isRTL); 1733 1734 int32_t n = numCells; 1735 while (n--) { 1736 finder.Scan(frame); 1737 if (finder.IsDone()) { 1738 break; 1739 } 1740 frame = frame->GetNextSibling(); 1741 } 1742 finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame); 1743 return NS_OK; 1744 } 1745 1746 // end nsLineIterator methods 1747 1748 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty, 1749 nsTableRowGroupFrame::FrameCursorData) 1750 1751 void nsTableRowGroupFrame::ClearRowCursor() { 1752 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { 1753 return; 1754 } 1755 1756 RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); 1757 RemoveProperty(RowCursorProperty()); 1758 } 1759 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; 1764 } 1765 1766 nsIFrame* f = mFrames.FirstChild(); 1767 int32_t count; 1768 for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) { 1769 f = f->GetNextSibling(); 1770 } 1771 if (!f) { 1772 // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother 1773 return nullptr; 1774 } 1775 1776 FrameCursorData* data = new FrameCursorData(); 1777 SetProperty(RowCursorProperty(), data); 1778 AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); 1779 return data; 1780 } 1781 1782 nsIFrame* nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, 1783 nscoord* aOverflowAbove) { 1784 if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { 1785 return nullptr; 1786 } 1787 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; 1793 } 1794 nsIFrame* cursorFrame = property->mFrames[cursorIndex]; 1795 1796 // The cursor's frame list excludes frames with empty overflow-area, so 1797 // we don't need to check that here. 1798 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]; 1808 } 1809 while (cursorIndex + 1 < frameCount && 1810 cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) { 1811 ++cursorIndex; 1812 cursorFrame = property->mFrames[cursorIndex]; 1813 } 1814 1815 property->mCursorIndex = cursorIndex; 1816 *aOverflowAbove = property->mOverflowAbove; 1817 return cursorFrame; 1818 } 1819 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; 1831 1832 nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect); 1833 if (overflowRect.IsEmpty()) { 1834 return true; 1835 } 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; 1844 } 1845 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); 1853 } 1854 } 1855 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); 1866 }