nsTableRowFrame.cpp (51448B)
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 6 #include "nsTableRowFrame.h" 7 8 #include <algorithm> 9 10 #include "mozilla/Baseline.h" 11 #include "mozilla/ComputedStyle.h" 12 #include "mozilla/Maybe.h" 13 #include "mozilla/PresShell.h" 14 #include "mozilla/StaticPrefs_layout.h" 15 #include "nsCSSRendering.h" 16 #include "nsDisplayList.h" 17 #include "nsHTMLParts.h" 18 #include "nsIContent.h" 19 #include "nsIFrame.h" 20 #include "nsIFrameInlines.h" 21 #include "nsPresContext.h" 22 #include "nsStyleConsts.h" 23 #include "nsTableCellFrame.h" 24 #include "nsTableColFrame.h" 25 #include "nsTableColGroupFrame.h" 26 #include "nsTableFrame.h" 27 #include "nsTableRowGroupFrame.h" 28 29 #ifdef ACCESSIBILITY 30 # include "nsAccessibilityService.h" 31 #endif 32 33 using namespace mozilla; 34 35 namespace mozilla { 36 37 struct TableCellReflowInput : public ReflowInput { 38 TableCellReflowInput(nsPresContext* aPresContext, 39 const ReflowInput& aParentReflowInput, nsIFrame* aFrame, 40 const LogicalSize& aAvailableSpace, 41 ReflowInput::InitFlags aFlags = {}) 42 : ReflowInput(aPresContext, aParentReflowInput, aFrame, aAvailableSpace, 43 Nothing(), aFlags) {} 44 45 void FixUp(const LogicalSize& aAvailSpace); 46 }; 47 48 } // namespace mozilla 49 50 void TableCellReflowInput::FixUp(const LogicalSize& aAvailSpace) { 51 // fix the mComputed values during a pass 2 reflow since the cell can be a 52 // percentage base 53 NS_WARNING_ASSERTION( 54 NS_UNCONSTRAINEDSIZE != aAvailSpace.ISize(mWritingMode), 55 "have unconstrained inline-size; this should only result from very large " 56 "sizes, not attempts at intrinsic inline size calculation"); 57 if (NS_UNCONSTRAINEDSIZE != ComputedISize()) { 58 nscoord computedISize = 59 aAvailSpace.ISize(mWritingMode) - 60 ComputedLogicalBorderPadding(mWritingMode).IStartEnd(mWritingMode); 61 computedISize = std::max(0, computedISize); 62 SetComputedISize(computedISize); 63 } 64 if (NS_UNCONSTRAINEDSIZE != ComputedBSize() && 65 NS_UNCONSTRAINEDSIZE != aAvailSpace.BSize(mWritingMode)) { 66 nscoord computedBSize = 67 aAvailSpace.BSize(mWritingMode) - 68 ComputedLogicalBorderPadding(mWritingMode).BStartEnd(mWritingMode); 69 computedBSize = std::max(0, computedBSize); 70 SetComputedBSize(computedBSize); 71 } 72 } 73 74 void nsTableRowFrame::InitChildReflowInput(nsPresContext& aPresContext, 75 const LogicalSize& aAvailSize, 76 bool aBorderCollapse, 77 TableCellReflowInput& aReflowInput) { 78 Maybe<LogicalMargin> collapseBorder; 79 if (aBorderCollapse) { 80 // we only reflow cells, so don't need to check frame type 81 nsBCTableCellFrame* bcCellFrame = (nsBCTableCellFrame*)aReflowInput.mFrame; 82 if (bcCellFrame) { 83 collapseBorder.emplace( 84 bcCellFrame->GetBorderWidth(aReflowInput.GetWritingMode())); 85 } 86 } 87 aReflowInput.Init(&aPresContext, Nothing(), collapseBorder); 88 aReflowInput.FixUp(aAvailSize); 89 } 90 91 void nsTableRowFrame::SetFixedBSize(nscoord aValue) { 92 nscoord bsize = std::max(0, aValue); 93 if (HasFixedBSize()) { 94 if (bsize > mStyleFixedBSize) { 95 mStyleFixedBSize = bsize; 96 } 97 } else { 98 mStyleFixedBSize = bsize; 99 if (bsize > 0) { 100 SetHasFixedBSize(true); 101 } 102 } 103 } 104 105 void nsTableRowFrame::SetPctBSize(float aPctValue, bool aForce) { 106 nscoord bsize = std::max(0, NSToCoordRound(aPctValue * 100.0f)); 107 if (HasPctBSize()) { 108 if ((bsize > mStylePctBSize) || aForce) { 109 mStylePctBSize = bsize; 110 } 111 } else { 112 mStylePctBSize = bsize; 113 if (bsize > 0) { 114 SetHasPctBSize(true); 115 } 116 } 117 } 118 119 /* ----------- nsTableRowFrame ---------- */ 120 121 NS_QUERYFRAME_HEAD(nsTableRowFrame) 122 NS_QUERYFRAME_ENTRY(nsTableRowFrame) 123 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 124 125 nsTableRowFrame::nsTableRowFrame(ComputedStyle* aStyle, 126 nsPresContext* aPresContext, ClassID aID) 127 : nsContainerFrame(aStyle, aPresContext, aID) { 128 mBits.mRowIndex = 0; 129 mBits.mHasFixedBSize = 0; 130 mBits.mHasPctBSize = 0; 131 mBits.mFirstInserted = 0; 132 ResetBSize(); 133 } 134 135 nsTableRowFrame::~nsTableRowFrame() = default; 136 137 void nsTableRowFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 138 nsIFrame* aPrevInFlow) { 139 // Let the base class do its initialization 140 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 141 142 NS_ASSERTION(mozilla::StyleDisplay::TableRow == StyleDisplay()->mDisplay, 143 "wrong display on table row frame"); 144 145 if (aPrevInFlow) { 146 // Set the row index 147 nsTableRowFrame* rowFrame = (nsTableRowFrame*)aPrevInFlow; 148 149 SetRowIndex(rowFrame->GetRowIndex()); 150 } else { 151 mWritingMode = GetTableFrame()->GetWritingMode(); 152 } 153 } 154 155 void nsTableRowFrame::Destroy(DestroyContext& aContext) { 156 nsTableFrame::MaybeUnregisterPositionedTablePart(this); 157 nsContainerFrame::Destroy(aContext); 158 } 159 160 /* virtual */ 161 void nsTableRowFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { 162 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); 163 nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle); 164 165 if (!aOldComputedStyle) { 166 return; // avoid the following on init 167 } 168 169 #ifdef ACCESSIBILITY 170 if (nsAccessibilityService* accService = GetAccService()) { 171 // If a table row's background color is now different from 172 // the background color of its previous row, it is possible our 173 // table now has alternating row colors. This changes whether or not 174 // the table is classified as a layout table or data table. 175 // We invalidate on every background color change to avoid 176 // walking the tree in search of the nearest row. 177 if (StyleBackground()->BackgroundColor(this) != 178 aOldComputedStyle->StyleBackground()->BackgroundColor( 179 aOldComputedStyle)) { 180 // We send a notification here to invalidate the a11y cache on the 181 // table so the next fetch of IsProbablyLayoutTable() is accurate. 182 accService->TableLayoutGuessMaybeChanged(PresShell(), mContent); 183 } 184 } 185 #endif 186 187 nsTableFrame* tableFrame = GetTableFrame(); 188 if (tableFrame->IsBorderCollapse() && 189 tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { 190 TableArea damageArea(0, GetRowIndex(), tableFrame->GetColCount(), 1); 191 tableFrame->AddBCDamageArea(damageArea); 192 } 193 } 194 195 void nsTableRowFrame::AppendFrames(ChildListID aListID, 196 nsFrameList&& aFrameList) { 197 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 198 199 DrainSelfOverflowList(); // ensure the last frame is in mFrames 200 const nsFrameList::Slice& newCells = 201 mFrames.AppendFrames(nullptr, std::move(aFrameList)); 202 203 // Add the new cell frames to the table 204 nsTableFrame* tableFrame = GetTableFrame(); 205 for (nsIFrame* childFrame : newCells) { 206 NS_ASSERTION(childFrame->IsTableCellFrame(), 207 "Not a table cell frame/pseudo frame construction failure"); 208 tableFrame->AppendCell(static_cast<nsTableCellFrame&>(*childFrame), 209 GetRowIndex()); 210 } 211 212 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 213 NS_FRAME_HAS_DIRTY_CHILDREN); 214 tableFrame->SetGeometryDirty(); 215 } 216 217 void nsTableRowFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 218 const nsLineList::iterator* aPrevFrameLine, 219 nsFrameList&& aFrameList) { 220 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 221 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 222 "inserting after sibling frame with different parent"); 223 if (mFrames.IsEmpty() || (aPrevFrame && !aPrevFrame->GetNextSibling())) { 224 // This is actually an append (though our caller didn't figure that out), 225 // and our append codepath is both simpler/faster _and_ less buggy. 226 // https://bugzilla.mozilla.org/show_bug.cgi?id=1388898 tracks the bugginess 227 AppendFrames(aListID, std::move(aFrameList)); 228 return; 229 } 230 231 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames 232 // Insert Frames in the frame list 233 const nsFrameList::Slice& newCells = 234 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); 235 236 nsTableCellFrame* prevCellFrame = 237 static_cast<nsTableCellFrame*>(nsTableFrame::GetFrameAtOrBefore( 238 this, aPrevFrame, LayoutFrameType::TableCell)); 239 nsTArray<nsTableCellFrame*> cellChildren; 240 for (nsIFrame* childFrame : newCells) { 241 NS_ASSERTION(childFrame->IsTableCellFrame(), 242 "Not a table cell frame/pseudo frame construction failure"); 243 cellChildren.AppendElement(static_cast<nsTableCellFrame*>(childFrame)); 244 } 245 // insert the cells into the cell map 246 int32_t colIndex = -1; 247 if (prevCellFrame) { 248 colIndex = prevCellFrame->ColIndex(); 249 } 250 nsTableFrame* tableFrame = GetTableFrame(); 251 tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex); 252 253 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 254 NS_FRAME_HAS_DIRTY_CHILDREN); 255 tableFrame->SetGeometryDirty(); 256 } 257 258 void nsTableRowFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, 259 nsIFrame* aOldFrame) { 260 NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); 261 MOZ_ASSERT((nsTableCellFrame*)do_QueryFrame(aOldFrame)); 262 263 auto* cellFrame = static_cast<nsTableCellFrame*>(aOldFrame); 264 // remove the cell from the cell map 265 nsTableFrame* tableFrame = GetTableFrame(); 266 tableFrame->RemoveCell(cellFrame, GetRowIndex()); 267 268 // Remove the frame and destroy it 269 mFrames.DestroyFrame(aContext, aOldFrame); 270 271 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 272 NS_FRAME_HAS_DIRTY_CHILDREN); 273 274 tableFrame->SetGeometryDirty(); 275 } 276 277 /* virtual */ 278 nsMargin nsTableRowFrame::GetUsedMargin() const { return nsMargin(0, 0, 0, 0); } 279 280 /* virtual */ 281 nsMargin nsTableRowFrame::GetUsedBorder() const { return nsMargin(0, 0, 0, 0); } 282 283 /* virtual */ 284 nsMargin nsTableRowFrame::GetUsedPadding() const { 285 return nsMargin(0, 0, 0, 0); 286 } 287 288 static nscoord GetBSizeOfRowsSpannedBelowFirst( 289 nsTableCellFrame& aTableCellFrame, nsTableFrame& aTableFrame, 290 const WritingMode aWM) { 291 nscoord bsize = 0; 292 int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(aTableCellFrame); 293 // add in bsize of rows spanned beyond the 1st one 294 nsIFrame* nextRow = aTableCellFrame.GetParent()->GetNextSibling(); 295 for (int32_t rowX = 1; ((rowX < rowSpan) && nextRow);) { 296 if (nextRow->IsTableRowFrame()) { 297 bsize += nextRow->BSize(aWM); 298 rowX++; 299 } 300 bsize += aTableFrame.GetRowSpacing(rowX); 301 nextRow = nextRow->GetNextSibling(); 302 } 303 return bsize; 304 } 305 306 /** 307 * Post-reflow hook. This is where the table row does its post-processing 308 */ 309 void nsTableRowFrame::DidResize(ForceAlignTopForTableCell aForceAlignTop) { 310 // Resize and re-align the cell frames based on our row bsize 311 nsTableFrame* tableFrame = GetTableFrame(); 312 313 WritingMode wm = GetWritingMode(); 314 ReflowOutput desiredSize(wm); 315 desiredSize.SetSize(wm, GetLogicalSize(wm)); 316 desiredSize.SetOverflowAreasToDesiredBounds(); 317 318 nsSize containerSize = mRect.Size(); 319 320 for (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; 321 cellFrame = cellFrame->GetNextCell()) { 322 nscoord cellBSize = BSize(wm) + GetBSizeOfRowsSpannedBelowFirst( 323 *cellFrame, *tableFrame, wm); 324 325 // If the bsize for the cell has changed, we need to reset it; 326 // and in vertical-rl mode, we need to update the cell's block position 327 // to account for the containerSize, which may not have been known 328 // earlier, so we always apply it here. 329 LogicalSize cellSize = cellFrame->GetLogicalSize(wm); 330 if (cellSize.BSize(wm) != cellBSize || wm.IsVerticalRL()) { 331 nsRect cellOldRect = cellFrame->GetRect(); 332 nsRect cellInkOverflow = cellFrame->InkOverflowRect(); 333 334 if (wm.IsVerticalRL()) { 335 // Get the old position of the cell, as we want to preserve its 336 // inline coordinate. 337 LogicalPoint oldPos = cellFrame->GetLogicalPosition(wm, containerSize); 338 339 // The cell should normally be aligned with the row's block-start, 340 // so set the B component of the position to zero: 341 LogicalPoint newPos(wm, oldPos.I(wm), 0); 342 343 // ...unless relative positioning is in effect, in which case the 344 // cell may have been moved away from the row's block-start 345 if (cellFrame->IsRelativelyOrStickyPositioned()) { 346 // Find out where the cell would have been without relative 347 // positioning. 348 LogicalPoint oldNormalPos = 349 cellFrame->GetLogicalNormalPosition(wm, containerSize); 350 // The difference (if any) between oldPos and oldNormalPos reflects 351 // relative positioning that was applied to the cell, and which we 352 // need to incorporate when resetting the position. 353 newPos.B(wm) = oldPos.B(wm) - oldNormalPos.B(wm); 354 } 355 356 if (oldPos != newPos) { 357 cellFrame->SetPosition(wm, newPos, containerSize); 358 } 359 } 360 361 cellSize.BSize(wm) = cellBSize; 362 cellFrame->SetSize(wm, cellSize); 363 364 if (tableFrame->IsBorderCollapse()) { 365 nsTableFrame::InvalidateTableFrame(cellFrame, cellOldRect, 366 cellInkOverflow, false); 367 } 368 } 369 370 // realign cell content based on the new bsize. We might be able to 371 // skip this if the bsize didn't change... maybe. Hard to tell. 372 cellFrame->AlignChildWithinCell(mMaxCellAscent, aForceAlignTop); 373 374 // Always store the overflow, even if the height didn't change, since 375 // we'll lose part of our overflow area otherwise. 376 ConsiderChildOverflow(desiredSize.mOverflowAreas, cellFrame); 377 378 // Note that if the cell's *content* needs to change in response 379 // to this height, it will get a special bsize reflow. 380 } 381 FinishAndStoreOverflow(&desiredSize); 382 // Let our base class do the usual work 383 } 384 385 // returns max-ascent amongst all cells that have 'vertical-align: baseline' 386 // *including* cells with rowspans 387 nscoord nsTableRowFrame::GetMaxCellAscent() const { return mMaxCellAscent; } 388 389 Maybe<nscoord> nsTableRowFrame::GetRowBaseline(WritingMode aWM) { 390 if (mMaxCellAscent) { 391 return Some(mMaxCellAscent); 392 } 393 394 // If we get here, we don't have a baseline on any of the cells in this row. 395 if (aWM.IsCentralBaseline()) { 396 return Nothing{}; 397 } 398 nscoord ascent = 0; 399 for (nsIFrame* childFrame : mFrames) { 400 MOZ_ASSERT(childFrame->IsTableCellFrame()); 401 nscoord s = Baseline::SynthesizeBOffsetFromContentBox( 402 childFrame, aWM, BaselineSharingGroup::First); 403 ascent = std::max(ascent, s); 404 } 405 return Some(ascent); 406 } 407 408 nscoord nsTableRowFrame::GetInitialBSize(nscoord aPctBasis) const { 409 nscoord bsize = 0; 410 if ((aPctBasis > 0) && HasPctBSize()) { 411 bsize = NSToCoordRound(GetPctBSize() * (float)aPctBasis); 412 } 413 if (HasFixedBSize()) { 414 bsize = std::max(bsize, GetFixedBSize()); 415 } 416 return std::max(bsize, GetContentBSize()); 417 } 418 419 void nsTableRowFrame::ResetBSize() { 420 SetHasFixedBSize(false); 421 SetHasPctBSize(false); 422 SetFixedBSize(0); 423 SetPctBSize(0); 424 SetContentBSize(0); 425 426 mMaxCellAscent = 0; 427 mMaxCellDescent = 0; 428 } 429 430 void nsTableRowFrame::UpdateBSize(nscoord aBSize, nsTableFrame* aTableFrame, 431 nsTableCellFrame* aCellFrame) { 432 if (!aTableFrame || !aCellFrame) { 433 MOZ_ASSERT_UNREACHABLE("Invalid call"); 434 return; 435 } 436 437 if (aBSize == NS_UNCONSTRAINEDSIZE) { 438 return; 439 } 440 441 if (GetInitialBSize() < aBSize && 442 aTableFrame->GetEffectiveRowSpan(*aCellFrame) == 1) { 443 SetContentBSize(aBSize); 444 } 445 446 if (aCellFrame->HasTableCellAlignmentBaseline()) { 447 if (auto ascent = aCellFrame->GetCellBaseline()) { 448 // see if this is a long ascender 449 if (mMaxCellAscent < *ascent) { 450 mMaxCellAscent = *ascent; 451 } 452 nscoord descent = aBSize - *ascent; 453 if (mMaxCellDescent < descent && 454 aTableFrame->GetEffectiveRowSpan(*aCellFrame) == 1) { 455 mMaxCellDescent = descent; 456 } 457 } 458 } 459 } 460 461 nscoord nsTableRowFrame::CalcBSize(const ReflowInput& aReflowInput) { 462 nsTableFrame* tableFrame = GetTableFrame(); 463 464 ResetBSize(); 465 const nscoord computedBSize = aReflowInput.ComputedBSize(); 466 if (computedBSize != NS_UNCONSTRAINEDSIZE && computedBSize > 0) { 467 SetFixedBSize(computedBSize); 468 } 469 470 WritingMode wm = aReflowInput.GetWritingMode(); 471 const nsStylePosition* position = StylePosition(); 472 const auto bsizeStyleCoord = 473 position->BSize(wm, AnchorPosResolutionParams::From(this)); 474 if (bsizeStyleCoord->ConvertsToLength()) { 475 SetFixedBSize(bsizeStyleCoord->ToLength()); 476 } else if (bsizeStyleCoord->ConvertsToPercentage()) { 477 SetPctBSize(bsizeStyleCoord->ToPercentage()); 478 } 479 480 for (nsTableCellFrame* kidFrame = GetFirstCell(); kidFrame; 481 kidFrame = kidFrame->GetNextCell()) { 482 MOZ_ASSERT(kidFrame->GetWritingMode() == wm); 483 LogicalSize desSize = kidFrame->GetDesiredSize(); 484 if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize() && 485 !GetPrevInFlow()) { 486 desSize.BSize(wm) = CalcCellActualBSize(kidFrame, desSize.BSize(wm), wm); 487 } 488 UpdateBSize(desSize.BSize(wm), tableFrame, kidFrame); 489 } 490 return GetInitialBSize(); 491 } 492 493 void nsTableRowFrame::PaintCellBackgroundsForFrame( 494 nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, 495 const nsDisplayListSet& aLists, const nsPoint& aOffset) { 496 // Compute background rect by iterating all cell frame. 497 const nsPoint toReferenceFrame = aBuilder->ToReferenceFrame(aFrame); 498 for (nsTableCellFrame* cell = GetFirstCell(); cell; 499 cell = cell->GetNextCell()) { 500 if (!cell->ShouldPaintBackground(aBuilder)) { 501 continue; 502 } 503 504 auto cellRect = 505 cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset; 506 if (!aBuilder->GetDirtyRect().Intersects(cellRect)) { 507 continue; 508 } 509 cellRect += toReferenceFrame; 510 nsDisplayBackgroundImage::AppendBackgroundItemsToTop( 511 aBuilder, aFrame, cellRect, aLists.BorderBackground(), true, 512 aFrame->GetRectRelativeToSelf() + toReferenceFrame, cell); 513 } 514 } 515 516 void nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 517 const nsDisplayListSet& aLists) { 518 DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground()); 519 520 PaintCellBackgroundsForFrame(this, aBuilder, aLists); 521 522 DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground()); 523 524 DisplayOutline(aBuilder, aLists); 525 526 if (mFrames.IsEmpty() || HidesContent()) { 527 return; 528 } 529 530 for (nsIFrame* kid : mFrames) { 531 BuildDisplayListForChild(aBuilder, kid, aLists); 532 } 533 } 534 535 LogicalSides nsTableRowFrame::GetLogicalSkipSides() const { 536 LogicalSides skip(mWritingMode); 537 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == 538 StyleBoxDecorationBreak::Clone)) { 539 return skip; 540 } 541 542 if (GetPrevInFlow()) { 543 skip += LogicalSide::BStart; 544 } 545 if (GetNextInFlow()) { 546 skip += LogicalSide::BEnd; 547 } 548 return skip; 549 } 550 551 nscoord nsTableRowFrame::CalcCellActualBSize(nsTableCellFrame* aCellFrame, 552 const nscoord& aDesiredBSize, 553 WritingMode aWM) { 554 nscoord specifiedBSize = 0; 555 556 // Get the bsize specified in the style information 557 const nsStylePosition* position = aCellFrame->StylePosition(); 558 559 int32_t rowSpan = GetTableFrame()->GetEffectiveRowSpan(*aCellFrame); 560 561 const auto bsizeStyleCoord = 562 position->BSize(aWM, AnchorPosResolutionParams::From(aCellFrame)); 563 if (bsizeStyleCoord->ConvertsToLength()) { 564 // In quirks mode, table cell bsize should always be border-box. 565 // https://quirks.spec.whatwg.org/#the-table-cell-height-box-sizing-quirk 566 specifiedBSize = bsizeStyleCoord->ToLength(); 567 if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks && 568 position->mBoxSizing == StyleBoxSizing::Content) { 569 specifiedBSize += 570 aCellFrame->GetLogicalUsedBorderAndPadding(aWM).BStartEnd(aWM); 571 } 572 573 if (1 == rowSpan) { 574 SetFixedBSize(specifiedBSize); 575 } 576 } else if (bsizeStyleCoord->ConvertsToPercentage()) { 577 if (1 == rowSpan) { 578 SetPctBSize(bsizeStyleCoord->ToPercentage()); 579 } 580 } 581 582 // If the specified bsize is greater than the desired bsize, 583 // then use the specified bsize 584 return std::max(specifiedBSize, aDesiredBSize); 585 } 586 587 // Calculates the available isize for the table cell based on the known 588 // column isizes taking into account column spans and column spacing 589 static nscoord CalcAvailISize(nsTableFrame& aTableFrame, 590 nsTableCellFrame& aCellFrame) { 591 nscoord cellAvailISize = 0; 592 uint32_t colIndex = aCellFrame.ColIndex(); 593 int32_t colspan = aTableFrame.GetEffectiveColSpan(aCellFrame); 594 NS_ASSERTION(colspan > 0, "effective colspan should be positive"); 595 nsTableFrame* fifTable = 596 static_cast<nsTableFrame*>(aTableFrame.FirstInFlow()); 597 598 for (int32_t spanX = 0; spanX < colspan; spanX++) { 599 cellAvailISize += fifTable->GetColumnISizeFromFirstInFlow(colIndex + spanX); 600 if (spanX > 0 && aTableFrame.ColumnHasCellSpacingBefore(colIndex + spanX)) { 601 cellAvailISize += aTableFrame.GetColSpacing(colIndex + spanX - 1); 602 } 603 } 604 return cellAvailISize; 605 } 606 607 static nscoord GetSpaceBetween(int32_t aPrevColIndex, int32_t aColIndex, 608 int32_t aColSpan, nsTableFrame& aTableFrame, 609 bool aCheckVisibility) { 610 nscoord space = 0; 611 int32_t colIdx; 612 nsTableFrame* fifTable = 613 static_cast<nsTableFrame*>(aTableFrame.FirstInFlow()); 614 for (colIdx = aPrevColIndex + 1; aColIndex > colIdx; colIdx++) { 615 bool isCollapsed = false; 616 if (!aCheckVisibility) { 617 space += fifTable->GetColumnISizeFromFirstInFlow(colIdx); 618 } else { 619 nsTableColFrame* colFrame = aTableFrame.GetColFrame(colIdx); 620 const nsStyleVisibility* colVis = colFrame->StyleVisibility(); 621 bool collapseCol = StyleVisibility::Collapse == colVis->mVisible; 622 nsIFrame* cgFrame = colFrame->GetParent(); 623 const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); 624 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; 625 isCollapsed = collapseCol || collapseGroup; 626 if (!isCollapsed) { 627 space += fifTable->GetColumnISizeFromFirstInFlow(colIdx); 628 } 629 } 630 if (!isCollapsed && aTableFrame.ColumnHasCellSpacingBefore(colIdx)) { 631 space += aTableFrame.GetColSpacing(colIdx - 1); 632 } 633 } 634 return space; 635 } 636 637 // subtract the bsizes of aRow's prev in flows from the unpaginated bsize 638 static nscoord CalcBSizeFromUnpaginatedBSize(nsTableRowFrame& aRow, 639 WritingMode aWM) { 640 nscoord bsize = 0; 641 nsTableRowFrame* firstInFlow = 642 static_cast<nsTableRowFrame*>(aRow.FirstInFlow()); 643 if (firstInFlow->HasUnpaginatedBSize()) { 644 bsize = firstInFlow->GetUnpaginatedBSize(); 645 for (nsIFrame* prevInFlow = aRow.GetPrevInFlow(); prevInFlow; 646 prevInFlow = prevInFlow->GetPrevInFlow()) { 647 bsize -= prevInFlow->BSize(aWM); 648 } 649 } 650 return std::max(bsize, 0); 651 } 652 653 void nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, 654 ReflowOutput& aDesiredSize, 655 const ReflowInput& aReflowInput, 656 nsTableFrame& aTableFrame, 657 nsReflowStatus& aStatus) { 658 aStatus.Reset(); 659 660 // XXXldb Should we be checking constrained bsize instead? 661 const bool isPaginated = aPresContext->IsPaginated(); 662 const bool borderCollapse = aTableFrame.IsBorderCollapse(); 663 664 int32_t cellColSpan = 665 1; // must be defined here so it's set properly for non-cell kids 666 667 // remember the col index of the previous cell to handle rowspans into this 668 // row 669 int32_t prevColIndex = -1; 670 nscoord iCoord = 0; // running total of children inline-coord offset 671 672 // This computes the max of all cell bsizes 673 nscoord cellMaxBSize = 0; 674 675 // Reflow each of our existing cell frames 676 WritingMode wm = aReflowInput.GetWritingMode(); 677 nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained(); 678 bool hasOrthogonalCell = false; 679 680 for (nsTableCellFrame* kidFrame = GetFirstCell(); kidFrame; 681 kidFrame = kidFrame->GetNextCell()) { 682 // If we have any cells with orthogonal content, we'll need to handle them 683 // later; record the presence of any such cells. 684 if (kidFrame->Inner()->GetWritingMode().IsOrthogonalTo(wm)) { 685 hasOrthogonalCell = true; 686 } 687 // See if we should only reflow the dirty child frames 688 bool doReflowChild = true; 689 if (!aReflowInput.ShouldReflowAllKids() && !aTableFrame.IsGeometryDirty() && 690 !kidFrame->IsSubtreeDirty()) { 691 if (!aReflowInput.mFlags.mSpecialBSizeReflow) { 692 doReflowChild = false; 693 } 694 } else if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize()) { 695 // We don't reflow a rowspan >1 cell here with a constrained bsize. 696 // That happens in nsTableRowGroupFrame::SplitSpanningCells. 697 if (aTableFrame.GetEffectiveRowSpan(*kidFrame) > 1) { 698 doReflowChild = false; 699 } 700 } 701 if (aReflowInput.mFlags.mSpecialBSizeReflow && !isPaginated && 702 !kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { 703 continue; 704 } 705 706 uint32_t cellColIndex = kidFrame->ColIndex(); 707 cellColSpan = aTableFrame.GetEffectiveColSpan(*kidFrame); 708 709 // If the adjacent cell is in a prior row (because of a rowspan) add in the 710 // space NOTE: prevColIndex can be -1 here. 711 if (prevColIndex != (static_cast<int32_t>(cellColIndex) - 1)) { 712 iCoord += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, 713 aTableFrame, false); 714 } 715 716 // remember the rightmost (ltr) or leftmost (rtl) column this cell spans 717 // into 718 prevColIndex = cellColIndex + (cellColSpan - 1); 719 720 // Reflow the child frame 721 nsRect kidRect = kidFrame->GetRect(); 722 LogicalPoint origKidNormalPosition = 723 kidFrame->GetLogicalNormalPosition(wm, containerSize); 724 725 nsRect kidInkOverflow = kidFrame->InkOverflowRect(); 726 LogicalPoint kidPosition(wm, iCoord, 0); 727 bool firstReflow = kidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 728 729 if (doReflowChild) { 730 // Calculate the available isize for the table cell using the known 731 // column isizes 732 nscoord availCellISize = CalcAvailISize(aTableFrame, *kidFrame); 733 734 Maybe<TableCellReflowInput> kidReflowInput; 735 ReflowOutput desiredSize(aReflowInput); 736 737 // If the avail isize is not the same as last time we reflowed the cell or 738 // the cell wants to be bigger than what was available last time or 739 // it is a style change reflow or we are printing, then we must reflow the 740 // cell. Otherwise we can skip the reflow. 741 // XXXldb Why is this condition distinct from doReflowChild above? 742 NS_ASSERTION(kidFrame->GetWritingMode() == wm, 743 "expected consistent writing-mode within table"); 744 LogicalSize cellDesiredSize = kidFrame->GetDesiredSize(); 745 if (availCellISize != kidFrame->GetPriorAvailISize() || 746 cellDesiredSize.ISize(wm) > kidFrame->GetPriorAvailISize() || 747 HasAnyStateBits(NS_FRAME_IS_DIRTY) || isPaginated || 748 kidFrame->IsSubtreeDirty() || 749 // See if it needs a special reflow, or if it had one that we need to 750 // undo. 751 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE) || 752 kidFrame->BCBordersChanged() || HasPctBSize()) { 753 // Reflow the cell to fit the available isize, bsize 754 // XXX The old IR_ChildIsDirty code used availCellISize here. 755 LogicalSize kidAvailSize(wm, availCellISize, 756 aReflowInput.AvailableBSize()); 757 758 // Reflow the child 759 kidReflowInput.emplace(aPresContext, aReflowInput, kidFrame, 760 kidAvailSize, 761 ReflowInput::InitFlag::CallerWillInit); 762 InitChildReflowInput(*aPresContext, kidAvailSize, borderCollapse, 763 *kidReflowInput); 764 765 nsReflowStatus status; 766 ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowInput, wm, 767 kidPosition, containerSize, ReflowChildFlags::Default, 768 status); 769 770 // allow the table to determine if/how the table needs to be rebalanced 771 // If any of the cells are not complete, then we're not complete 772 if (status.IsIncomplete()) { 773 aStatus.Reset(); 774 aStatus.SetIncomplete(); 775 } 776 } else { 777 if (iCoord != origKidNormalPosition.I(wm)) { 778 kidFrame->InvalidateFrameSubtree(); 779 } 780 781 desiredSize.SetSize(wm, cellDesiredSize); 782 desiredSize.mOverflowAreas = kidFrame->GetOverflowAreas(); 783 } 784 785 if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) { 786 if (!GetPrevInFlow()) { 787 desiredSize.BSize(wm) = 788 CalcCellActualBSize(kidFrame, desiredSize.BSize(wm), wm); 789 } 790 // bsize may have changed, adjust descent to absorb any excess 791 // difference 792 UpdateBSize(desiredSize.BSize(wm), &aTableFrame, kidFrame); 793 } else { 794 cellMaxBSize = std::max(cellMaxBSize, desiredSize.BSize(wm)); 795 int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*kidFrame); 796 if (1 == rowSpan) { 797 SetContentBSize(cellMaxBSize); 798 } 799 } 800 801 // Place the child 802 desiredSize.ISize(wm) = availCellISize; 803 804 ReflowChildFlags flags = ReflowChildFlags::Default; 805 806 if (kidReflowInput) { 807 // We reflowed. Apply relative positioning in the normal way. 808 flags = ReflowChildFlags::ApplyRelativePositioning; 809 } else if (kidFrame->IsRelativelyOrStickyPositioned()) { 810 // We didn't reflow. Do the positioning part of what 811 // MovePositionBy does internally. (This codepath should really 812 // be merged into the else below if we can.) 813 nsMargin* computedOffsetProp = 814 kidFrame->GetProperty(nsIFrame::ComputedOffsetProperty()); 815 816 // On our fist reflow sticky children may not have the property yet (we 817 // need to reflow the children first to size the scroll frame). 818 LogicalMargin computedOffsets( 819 wm, computedOffsetProp ? *computedOffsetProp : nsMargin()); 820 ReflowInput::ApplyRelativePositioning(kidFrame, wm, computedOffsets, 821 &kidPosition, containerSize); 822 } 823 824 // In vertical-rl mode, we are likely to have containerSize.width = 0 825 // because ComputedWidth() was NS_UNCONSTRAINEDSIZE. 826 // For cases where that's wrong, we will fix up the position later. 827 FinishReflowChild(kidFrame, aPresContext, desiredSize, 828 kidReflowInput.ptrOr(nullptr), wm, kidPosition, 829 containerSize, flags); 830 831 nsTableFrame* tableFrame = GetTableFrame(); 832 if (tableFrame->IsBorderCollapse()) { 833 nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidInkOverflow, 834 firstReflow); 835 } 836 837 iCoord += desiredSize.ISize(wm); 838 } else { 839 if (iCoord != origKidNormalPosition.I(wm)) { 840 // Invalidate the old position 841 kidFrame->InvalidateFrameSubtree(); 842 // Move to the new position. As above, we need to account for relative 843 // positioning. 844 kidFrame->MovePositionBy( 845 wm, LogicalPoint(wm, iCoord - origKidNormalPosition.I(wm), 0)); 846 // invalidate the new position 847 kidFrame->InvalidateFrameSubtree(); 848 } 849 // we need to account for the cell's isize even if it isn't reflowed 850 iCoord += kidFrame->ISize(wm); 851 852 if (kidFrame->GetNextInFlow()) { 853 aStatus.Reset(); 854 aStatus.SetIncomplete(); 855 } 856 } 857 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); 858 iCoord += aTableFrame.GetColSpacing(cellColIndex); 859 } 860 861 // Just set our isize to what was available. 862 // The table will calculate the isize and not use our value. 863 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); 864 865 if (aReflowInput.mFlags.mSpecialBSizeReflow) { 866 aDesiredSize.BSize(wm) = BSize(wm); 867 } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) { 868 aDesiredSize.BSize(wm) = CalcBSize(aReflowInput); 869 if (GetPrevInFlow()) { 870 nscoord bsize = CalcBSizeFromUnpaginatedBSize(*this, wm); 871 aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bsize); 872 } else { 873 if (isPaginated && HasStyleBSize()) { 874 // set the unpaginated bsize so next in flows can try to honor it 875 SetUnpaginatedBSize(aDesiredSize.BSize(wm)); 876 } 877 if (isPaginated && HasUnpaginatedBSize()) { 878 aDesiredSize.BSize(wm) = 879 std::max(aDesiredSize.BSize(wm), GetUnpaginatedBSize()); 880 } 881 } 882 } else { // constrained bsize, paginated 883 // Compute the bsize we should have from style (subtracting the 884 // bsize from our prev-in-flows from the style bsize) 885 nscoord styleBSize = CalcBSizeFromUnpaginatedBSize(*this, wm); 886 if (styleBSize > aReflowInput.AvailableBSize()) { 887 styleBSize = aReflowInput.AvailableBSize(); 888 aStatus.SetIncomplete(); 889 } 890 aDesiredSize.BSize(wm) = std::max(cellMaxBSize, styleBSize); 891 } 892 893 if (wm.IsVerticalRL()) { 894 // Any children whose width was not the same as our final 895 // aDesiredSize.BSize will have been misplaced earlier at the 896 // FinishReflowChild stage. So fix them up now. 897 for (nsIFrame* kidFrame : mFrames) { 898 if (kidFrame->BSize(wm) != aDesiredSize.BSize(wm)) { 899 kidFrame->MovePositionBy( 900 wm, 901 LogicalPoint(wm, 0, kidFrame->BSize(wm) - aDesiredSize.BSize(wm))); 902 // Do we need to InvalidateFrameSubtree() here? 903 } 904 } 905 } 906 907 aDesiredSize.UnionOverflowAreasWithDesiredBounds(); 908 FinishAndStoreOverflow(&aDesiredSize); 909 910 if (hasOrthogonalCell) { 911 for (nsTableCellFrame* kidFrame = GetFirstCell(); kidFrame; 912 kidFrame = kidFrame->GetNextCell()) { 913 if (kidFrame->Inner()->GetWritingMode().IsOrthogonalTo(wm)) { 914 LogicalSize kidAvailSize(wm, kidFrame->GetRectRelativeToSelf().Size()); 915 kidAvailSize.BSize(wm) = aDesiredSize.BSize(wm); 916 917 // Reflow the child 918 TableCellReflowInput kidReflowInput( 919 aPresContext, aReflowInput, kidFrame, kidAvailSize, 920 ReflowInput::InitFlag::CallerWillInit); 921 kidReflowInput.mFlags.mOrthogonalCellFinalReflow = true; 922 InitChildReflowInput(*aPresContext, kidAvailSize, borderCollapse, 923 kidReflowInput); 924 925 nsReflowStatus status; 926 ReflowOutput reflowOutput(wm); 927 ReflowChild(kidFrame, aPresContext, reflowOutput, kidReflowInput, wm, 928 kidFrame->GetLogicalPosition(containerSize), containerSize, 929 ReflowChildFlags::Default, status); 930 } 931 } 932 } 933 } 934 935 /** Layout the entire row. 936 * This method stacks cells in the inline dir according to HTML 4.0 rules. 937 */ 938 void nsTableRowFrame::Reflow(nsPresContext* aPresContext, 939 ReflowOutput& aDesiredSize, 940 const ReflowInput& aReflowInput, 941 nsReflowStatus& aStatus) { 942 MarkInReflow(); 943 DO_GLOBAL_REFLOW_COUNT("nsTableRowFrame"); 944 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 945 946 WritingMode wm = aReflowInput.GetWritingMode(); 947 948 nsTableFrame* tableFrame = GetTableFrame(); 949 const nsStyleVisibility* rowVis = StyleVisibility(); 950 bool collapseRow = StyleVisibility::Collapse == rowVis->mVisible; 951 if (collapseRow) { 952 tableFrame->SetNeedToCollapse(true); 953 } 954 955 // see if a special bsize reflow needs to occur due to having a pct bsize 956 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput); 957 958 // See if we have a cell with specified/pct bsize 959 InitHasCellWithStyleBSize(tableFrame); 960 961 ReflowChildren(aPresContext, aDesiredSize, aReflowInput, *tableFrame, 962 aStatus); 963 964 if (aPresContext->IsPaginated() && !aStatus.IsFullyComplete() && 965 ShouldAvoidBreakInside(aReflowInput)) { 966 aStatus.SetInlineLineBreakBeforeAndReset(); 967 } 968 969 // Just set our isize to what was available. 970 // The table will calculate the isize and not use our value. 971 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); 972 973 // If our parent is in initial reflow, it'll handle invalidating our 974 // entire overflow rect. 975 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && 976 nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { 977 InvalidateFrame(); 978 } 979 980 // Any absolutely-positioned children will get reflowed in 981 // nsIFrame::FixupPositionedTableParts in another pass, so propagate our 982 // dirtiness to them before our parent clears our dirty bits. 983 PushDirtyBitToAbsoluteFrames(); 984 } 985 986 nscoord nsTableRowFrame::ReflowCellFrame(nsPresContext* aPresContext, 987 const ReflowInput& aReflowInput, 988 bool aIsTopOfPage, 989 nsTableCellFrame* aCellFrame, 990 nscoord aAvailableBSize, 991 nsReflowStatus& aStatus) { 992 MOZ_ASSERT(aPresContext->IsPaginated(), 993 "ReflowCellFrame currently supports only paged media!"); 994 MOZ_ASSERT(aAvailableBSize != NS_UNCONSTRAINEDSIZE, 995 "Why split cell frame if available bsize is unconstrained?"); 996 WritingMode wm = aReflowInput.GetWritingMode(); 997 998 // Reflow the cell frame with the specified height. Use the existing width 999 nsSize containerSize = aCellFrame->GetSize(); 1000 LogicalRect cellRect = aCellFrame->GetLogicalRect(wm, containerSize); 1001 nsRect cellInkOverflow = aCellFrame->InkOverflowRect(); 1002 1003 LogicalSize cellSize = cellRect.Size(wm); 1004 LogicalSize availSize(wm, cellRect.ISize(wm), aAvailableBSize); 1005 bool borderCollapse = GetTableFrame()->IsBorderCollapse(); 1006 NS_ASSERTION(aCellFrame->GetWritingMode() == wm, 1007 "expected consistent writing-mode within table"); 1008 TableCellReflowInput cellReflowInput(aPresContext, aReflowInput, aCellFrame, 1009 availSize, 1010 ReflowInput::InitFlag::CallerWillInit); 1011 InitChildReflowInput(*aPresContext, availSize, borderCollapse, 1012 cellReflowInput); 1013 cellReflowInput.mFlags.mIsTopOfPage = aIsTopOfPage; 1014 1015 ReflowOutput desiredSize(aReflowInput); 1016 1017 ReflowChild(aCellFrame, aPresContext, desiredSize, cellReflowInput, 0, 0, 1018 ReflowChildFlags::NoMoveFrame, aStatus); 1019 const bool isTruncated = 1020 aAvailableBSize < desiredSize.BSize(wm) && 1021 !aIsTopOfPage; // XXX Is !aIsTopOfPage check really necessary? 1022 const bool isCompleteAndNotTruncated = aStatus.IsComplete() && !isTruncated; 1023 if (isCompleteAndNotTruncated) { 1024 desiredSize.BSize(wm) = aAvailableBSize; 1025 } 1026 aCellFrame->SetSize( 1027 wm, LogicalSize(wm, cellSize.ISize(wm), desiredSize.BSize(wm))); 1028 1029 // Note: AlignChildWithinCell can affect the overflow rect. 1030 // XXX What happens if this cell has 'vertical-align: baseline' ? 1031 // XXX Why is it assumed that the cell's ascent hasn't changed ? 1032 if (isCompleteAndNotTruncated) { 1033 aCellFrame->AlignChildWithinCell(mMaxCellAscent, 1034 ForceAlignTopForTableCell::Yes); 1035 } 1036 1037 nsTableFrame::InvalidateTableFrame( 1038 aCellFrame, cellRect.GetPhysicalRect(wm, containerSize), cellInkOverflow, 1039 aCellFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)); 1040 1041 aCellFrame->DidReflow(aPresContext, nullptr); 1042 1043 return desiredSize.BSize(wm); 1044 } 1045 1046 nscoord nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset, 1047 nscoord aISize, 1048 bool aCollapseGroup, 1049 bool& aDidCollapse) { 1050 const nsStyleVisibility* rowVis = StyleVisibility(); 1051 bool collapseRow = StyleVisibility::Collapse == rowVis->mVisible; 1052 nsTableFrame* tableFrame = 1053 static_cast<nsTableFrame*>(GetTableFrame()->FirstInFlow()); 1054 if (collapseRow) { 1055 tableFrame->SetNeedToCollapse(true); 1056 } 1057 1058 if (aRowOffset != 0) { 1059 // We're moving, so invalidate our old position 1060 InvalidateFrameSubtree(); 1061 } 1062 1063 WritingMode wm = GetWritingMode(); 1064 1065 nsSize parentSize = GetParent()->GetSize(); 1066 LogicalRect rowRect = GetLogicalRect(wm, parentSize); 1067 nsRect oldRect = mRect; 1068 nsRect oldInkOverflow = InkOverflowRect(); 1069 1070 rowRect.BStart(wm) -= aRowOffset; 1071 rowRect.ISize(wm) = aISize; 1072 OverflowAreas overflow; 1073 nscoord shift = 0; 1074 nsSize containerSize = mRect.Size(); 1075 1076 if (aCollapseGroup || collapseRow) { 1077 aDidCollapse = true; 1078 shift = rowRect.BSize(wm); 1079 nsTableCellFrame* cellFrame = GetFirstCell(); 1080 if (cellFrame) { 1081 uint32_t rowIndex = cellFrame->RowIndex(); 1082 shift += tableFrame->GetRowSpacing(rowIndex); 1083 while (cellFrame) { 1084 LogicalRect cRect = cellFrame->GetLogicalRect(wm, containerSize); 1085 // If aRowOffset != 0, there's no point in invalidating the cells, since 1086 // we've already invalidated our overflow area. Note that we _do_ still 1087 // need to invalidate if our row is not moving, because the cell might 1088 // span out of this row, so invalidating our row rect won't do enough. 1089 if (aRowOffset == 0) { 1090 InvalidateFrame(); 1091 } 1092 cRect.BSize(wm) = 0; 1093 cellFrame->SetRect(wm, cRect, containerSize); 1094 cellFrame = cellFrame->GetNextCell(); 1095 } 1096 } else { 1097 shift += tableFrame->GetRowSpacing(GetRowIndex()); 1098 } 1099 rowRect.BSize(wm) = 0; 1100 } else { // row is not collapsed 1101 // remember the col index of the previous cell to handle rowspans into this 1102 // row 1103 int32_t prevColIndex = -1; 1104 nscoord iPos = 0; // running total of children inline-axis offset 1105 nsTableFrame* fifTable = 1106 static_cast<nsTableFrame*>(tableFrame->FirstInFlow()); 1107 1108 for (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; 1109 cellFrame = cellFrame->GetNextCell()) { 1110 uint32_t cellColIndex = cellFrame->ColIndex(); 1111 int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame); 1112 1113 // If the adjacent cell is in a prior row (because of a rowspan) add in 1114 // the space 1115 // NOTE: prevColIndex can be -1 here. 1116 if (prevColIndex != (static_cast<int32_t>(cellColIndex) - 1)) { 1117 iPos += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, 1118 *tableFrame, true); 1119 } 1120 LogicalRect cRect(wm, iPos, 0, 0, rowRect.BSize(wm)); 1121 1122 // remember the last (iend-wards-most) column this cell spans into 1123 prevColIndex = cellColIndex + cellColSpan - 1; 1124 int32_t actualColSpan = cellColSpan; 1125 bool isVisible = false; 1126 for (int32_t colIdx = cellColIndex; actualColSpan > 0; 1127 colIdx++, actualColSpan--) { 1128 nsTableColFrame* colFrame = tableFrame->GetColFrame(colIdx); 1129 const nsStyleVisibility* colVis = colFrame->StyleVisibility(); 1130 bool collapseCol = StyleVisibility::Collapse == colVis->mVisible; 1131 nsIFrame* cgFrame = colFrame->GetParent(); 1132 const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); 1133 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; 1134 bool isCollapsed = collapseCol || collapseGroup; 1135 if (!isCollapsed) { 1136 cRect.ISize(wm) += fifTable->GetColumnISizeFromFirstInFlow(colIdx); 1137 isVisible = true; 1138 if ((actualColSpan > 1)) { 1139 nsTableColFrame* nextColFrame = tableFrame->GetColFrame(colIdx + 1); 1140 const nsStyleVisibility* nextColVis = 1141 nextColFrame->StyleVisibility(); 1142 if (StyleVisibility::Collapse != nextColVis->mVisible && 1143 tableFrame->ColumnHasCellSpacingBefore(colIdx + 1)) { 1144 cRect.ISize(wm) += tableFrame->GetColSpacing(cellColIndex); 1145 } 1146 } 1147 } 1148 } 1149 iPos += cRect.ISize(wm); 1150 if (isVisible) { 1151 iPos += tableFrame->GetColSpacing(cellColIndex); 1152 } 1153 int32_t actualRowSpan = tableFrame->GetEffectiveRowSpan(*cellFrame); 1154 nsTableRowFrame* rowFrame = GetNextRow(); 1155 for (actualRowSpan--; actualRowSpan > 0 && rowFrame; actualRowSpan--) { 1156 const nsStyleVisibility* nextRowVis = rowFrame->StyleVisibility(); 1157 bool collapseNextRow = 1158 StyleVisibility::Collapse == nextRowVis->mVisible; 1159 if (!collapseNextRow) { 1160 LogicalRect nextRect = rowFrame->GetLogicalRect(wm, containerSize); 1161 cRect.BSize(wm) += nextRect.BSize(wm) + 1162 tableFrame->GetRowSpacing(rowFrame->GetRowIndex()); 1163 } 1164 rowFrame = rowFrame->GetNextRow(); 1165 } 1166 1167 nsRect oldCellRect = cellFrame->GetRect(); 1168 LogicalPoint oldCellNormalPos = 1169 cellFrame->GetLogicalNormalPosition(wm, containerSize); 1170 1171 nsRect oldCellInkOverflow = cellFrame->InkOverflowRect(); 1172 1173 if (aRowOffset == 0 && cRect.Origin(wm) != oldCellNormalPos) { 1174 // We're moving the cell. Invalidate the old overflow area 1175 cellFrame->InvalidateFrameSubtree(); 1176 } 1177 1178 cellFrame->MovePositionBy(wm, cRect.Origin(wm) - oldCellNormalPos); 1179 cellFrame->SetSize(wm, cRect.Size(wm)); 1180 1181 // XXXbz This looks completely bogus in the cases when we didn't 1182 // collapse the cell! 1183 LogicalRect cellBounds(wm, 0, 0, cRect.ISize(wm), cRect.BSize(wm)); 1184 nsRect cellPhysicalBounds = cellBounds.GetPhysicalRect(wm, containerSize); 1185 OverflowAreas cellOverflow(cellPhysicalBounds, cellPhysicalBounds); 1186 cellFrame->FinishAndStoreOverflow(cellOverflow, 1187 cRect.Size(wm).GetPhysicalSize(wm)); 1188 ConsiderChildOverflow(overflow, cellFrame); 1189 1190 if (aRowOffset == 0) { 1191 nsTableFrame::InvalidateTableFrame(cellFrame, oldCellRect, 1192 oldCellInkOverflow, false); 1193 } 1194 } 1195 } 1196 1197 SetRect(wm, rowRect, containerSize); 1198 overflow.UnionAllWith(nsRect(0, 0, rowRect.Width(wm), rowRect.Height(wm))); 1199 FinishAndStoreOverflow(overflow, rowRect.Size(wm).GetPhysicalSize(wm)); 1200 nsTableFrame::InvalidateTableFrame(this, oldRect, oldInkOverflow, false); 1201 return shift; 1202 } 1203 1204 /* 1205 * The following method is called by the row group frame's SplitRowGroup() 1206 * when it creates a continuing cell frame and wants to insert it into the 1207 * row's child list. 1208 */ 1209 void nsTableRowFrame::InsertCellFrame(nsTableCellFrame* aFrame, 1210 int32_t aColIndex) { 1211 // Find the cell frame where col index < aColIndex 1212 nsTableCellFrame* priorCell = nullptr; 1213 1214 for (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; 1215 cellFrame = cellFrame->GetNextCell()) { 1216 uint32_t colIndex = cellFrame->ColIndex(); 1217 // Can aColIndex be -1 here? Let's assume it can for now. 1218 if (static_cast<int32_t>(colIndex) < aColIndex) { 1219 priorCell = cellFrame; 1220 } else { 1221 break; 1222 } 1223 } 1224 mFrames.InsertFrame(this, priorCell, aFrame); 1225 } 1226 1227 nsTableRowFrame* nsTableRowFrame::GetPrevRow() const { 1228 nsIFrame* prevSibling = GetPrevSibling(); 1229 MOZ_ASSERT( 1230 !prevSibling || static_cast<nsTableRowFrame*>(do_QueryFrame(prevSibling)), 1231 "How do we have a non-row sibling?"); 1232 return static_cast<nsTableRowFrame*>(prevSibling); 1233 } 1234 1235 nsTableRowFrame* nsTableRowFrame::GetNextRow() const { 1236 nsIFrame* nextSibling = GetNextSibling(); 1237 MOZ_ASSERT( 1238 !nextSibling || static_cast<nsTableRowFrame*>(do_QueryFrame(nextSibling)), 1239 "How do we have a non-row sibling?"); 1240 return static_cast<nsTableRowFrame*>(nextSibling); 1241 } 1242 1243 // This property is only set on the first-in-flow of nsTableRowFrame. 1244 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TableRowUnpaginatedBSizeProperty, nscoord) 1245 1246 void nsTableRowFrame::SetUnpaginatedBSize(nscoord aValue) { 1247 MOZ_ASSERT(!GetPrevInFlow(), 1248 "TableRowUnpaginatedBSizeProperty should only be set on the " 1249 "first-in-flow!"); 1250 AddStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE); 1251 SetProperty(TableRowUnpaginatedBSizeProperty(), aValue); 1252 } 1253 1254 nscoord nsTableRowFrame::GetUnpaginatedBSize() const { 1255 return GetProperty(TableRowUnpaginatedBSizeProperty()); 1256 } 1257 1258 #ifdef ACCESSIBILITY 1259 a11y::AccType nsTableRowFrame::AccessibleType() { 1260 return a11y::eHTMLTableRowType; 1261 } 1262 #endif 1263 /** 1264 * Sets the NS_ROW_HAS_CELL_WITH_STYLE_BSIZE bit to indicate whether 1265 * this row has any cells that have non-auto-bsize. (Row-spanning 1266 * cells are ignored.) 1267 */ 1268 void nsTableRowFrame::InitHasCellWithStyleBSize(nsTableFrame* aTableFrame) { 1269 WritingMode wm = GetWritingMode(); 1270 1271 for (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; 1272 cellFrame = cellFrame->GetNextCell()) { 1273 // Ignore row-spanning cells 1274 const auto cellBSize = cellFrame->StylePosition()->BSize( 1275 wm, AnchorPosResolutionParams::From(cellFrame)); 1276 if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 && 1277 !cellBSize->IsAuto() && 1278 /* calc() with both percentages and lengths treated like 'auto' */ 1279 (cellBSize->ConvertsToLength() || cellBSize->ConvertsToPercentage())) { 1280 AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE); 1281 return; 1282 } 1283 } 1284 RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE); 1285 } 1286 1287 void nsTableRowFrame::InvalidateFrame(uint32_t aDisplayItemKey, 1288 bool aRebuildDisplayItems) { 1289 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); 1290 if (GetTableFrame()->IsBorderCollapse()) { 1291 const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); 1292 GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), 1293 aDisplayItemKey, rebuild); 1294 } 1295 } 1296 1297 void nsTableRowFrame::InvalidateFrameWithRect(const nsRect& aRect, 1298 uint32_t aDisplayItemKey, 1299 bool aRebuildDisplayItems) { 1300 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, 1301 aRebuildDisplayItems); 1302 // If we have filters applied that would affects our bounds, then 1303 // we get an inactive layer created and this is computed 1304 // within FrameLayerBuilder 1305 GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey, 1306 aRebuildDisplayItems); 1307 } 1308 1309 /* ----- global methods ----- */ 1310 1311 nsTableRowFrame* NS_NewTableRowFrame(PresShell* aPresShell, 1312 ComputedStyle* aStyle) { 1313 return new (aPresShell) nsTableRowFrame(aStyle, aPresShell->GetPresContext()); 1314 } 1315 1316 NS_IMPL_FRAMEARENA_HELPERS(nsTableRowFrame) 1317 1318 #ifdef DEBUG_FRAME_DUMP 1319 nsresult nsTableRowFrame::GetFrameName(nsAString& aResult) const { 1320 return MakeFrameName(u"TableRow"_ns, aResult); 1321 } 1322 #endif