nsTableFrame.cpp (288180B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 sw=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsTableFrame.h" 8 9 #include <algorithm> 10 11 #include "BasicTableLayoutStrategy.h" 12 #include "FixedTableLayoutStrategy.h" 13 #include "gfxContext.h" 14 #include "mozilla/ComputedStyle.h" 15 #include "mozilla/IntegerRange.h" 16 #include "mozilla/Likely.h" 17 #include "mozilla/MathAlgorithms.h" 18 #include "mozilla/PresShell.h" 19 #include "mozilla/PresShellInlines.h" 20 #include "mozilla/Range.h" 21 #include "mozilla/RestyleManager.h" 22 #include "mozilla/ServoStyleSet.h" 23 #include "mozilla/WritingModes.h" 24 #include "mozilla/gfx/2D.h" 25 #include "mozilla/gfx/Helpers.h" 26 #include "mozilla/layers/RenderRootStateManager.h" 27 #include "mozilla/layers/StackingContextHelper.h" 28 #include "nsCOMPtr.h" 29 #include "nsCSSAnonBoxes.h" 30 #include "nsCSSFrameConstructor.h" 31 #include "nsCSSProps.h" 32 #include "nsCSSRendering.h" 33 #include "nsCellMap.h" 34 #include "nsContentUtils.h" 35 #include "nsDisplayList.h" 36 #include "nsError.h" 37 #include "nsFrameList.h" 38 #include "nsFrameManager.h" 39 #include "nsGkAtoms.h" 40 #include "nsHTMLParts.h" 41 #include "nsIContent.h" 42 #include "nsIFrameInlines.h" 43 #include "nsIScriptError.h" 44 #include "nsLayoutUtils.h" 45 #include "nsPresContext.h" 46 #include "nsStyleChangeList.h" 47 #include "nsStyleConsts.h" 48 #include "nsTableCellFrame.h" 49 #include "nsTableColFrame.h" 50 #include "nsTableColGroupFrame.h" 51 #include "nsTableRowFrame.h" 52 #include "nsTableRowGroupFrame.h" 53 #include "nsTableWrapperFrame.h" 54 55 using namespace mozilla; 56 using namespace mozilla::image; 57 using namespace mozilla::layout; 58 59 using mozilla::gfx::AutoRestoreTransform; 60 using mozilla::gfx::DrawTarget; 61 using mozilla::gfx::Float; 62 using mozilla::gfx::ToDeviceColor; 63 64 namespace mozilla { 65 66 struct TableReflowInput final { 67 TableReflowInput(const ReflowInput& aReflowInput, 68 const LogicalMargin& aBorderPadding, TableReflowMode aMode) 69 : mReflowInput(aReflowInput), 70 mWM(aReflowInput.GetWritingMode()), 71 mAvailSize(mWM) { 72 MOZ_ASSERT(mReflowInput.mFrame->IsTableFrame(), 73 "TableReflowInput should only be created for nsTableFrame"); 74 auto* table = static_cast<nsTableFrame*>(mReflowInput.mFrame); 75 76 mICoord = aBorderPadding.IStart(mWM) + table->GetColSpacing(-1); 77 mAvailSize.ISize(mWM) = 78 std::max(0, mReflowInput.ComputedISize() - table->GetColSpacing(-1) - 79 table->GetColSpacing(table->GetColCount())); 80 81 mAvailSize.BSize(mWM) = aMode == TableReflowMode::Measuring 82 ? NS_UNCONSTRAINEDSIZE 83 : mReflowInput.AvailableBSize(); 84 AdvanceBCoord(aBorderPadding.BStart(mWM) + 85 (!table->GetPrevInFlow() ? table->GetRowSpacing(-1) : 0)); 86 if (aReflowInput.mStyleBorder->mBoxDecorationBreak == 87 StyleBoxDecorationBreak::Clone) { 88 // At this point, we're assuming we won't be the last fragment, so we only 89 // reserve space for block-end border-padding if we're cloning it on each 90 // fragment; and we don't need to reserve any row-spacing for this 91 // hypothetical fragmentation, either. 92 ReduceAvailableBSizeBy(aBorderPadding.BEnd(mWM)); 93 } 94 } 95 96 // Advance to the next block-offset and reduce the available block-size. 97 void AdvanceBCoord(nscoord aAmount) { 98 mBCoord += aAmount; 99 ReduceAvailableBSizeBy(aAmount); 100 } 101 102 const LogicalSize& AvailableSize() const { return mAvailSize; } 103 104 // The real reflow input of the table frame. 105 const ReflowInput& mReflowInput; 106 107 // Stationary inline-offset, which won't change after the constructor. 108 nscoord mICoord = 0; 109 110 // Running block-offset, which will be adjusted as we reflow children. 111 nscoord mBCoord = 0; 112 113 private: 114 void ReduceAvailableBSizeBy(nscoord aAmount) { 115 if (mAvailSize.BSize(mWM) == NS_UNCONSTRAINEDSIZE) { 116 return; 117 } 118 mAvailSize.BSize(mWM) -= aAmount; 119 mAvailSize.BSize(mWM) = std::max(0, mAvailSize.BSize(mWM)); 120 } 121 122 // mReflowInput's (i.e. table frame's) writing-mode. 123 WritingMode mWM; 124 125 // The available size for children. The inline-size is stationary after the 126 // constructor, but the block-size will be adjusted as we reflow children. 127 LogicalSize mAvailSize; 128 }; 129 130 struct TableBCData final { 131 TableArea mDamageArea; 132 nscoord mBStartBorderWidth = 0; 133 nscoord mIEndBorderWidth = 0; 134 nscoord mBEndBorderWidth = 0; 135 nscoord mIStartBorderWidth = 0; 136 }; 137 138 } // namespace mozilla 139 140 /******************************************************************************** 141 ** nsTableFrame ** 142 ********************************************************************************/ 143 144 ComputedStyle* nsTableFrame::GetParentComputedStyle( 145 nsIFrame** aProviderFrame) const { 146 // Since our parent, the table wrapper frame, returned this frame, we 147 // must return whatever our parent would normally have returned. 148 149 MOZ_ASSERT(GetParent(), "table constructed without table wrapper"); 150 if (!mContent->GetParent() && !Style()->IsPseudoOrAnonBox()) { 151 // We're the root. We have no ComputedStyle parent. 152 *aProviderFrame = nullptr; 153 return nullptr; 154 } 155 156 return GetParent()->DoGetParentComputedStyle(aProviderFrame); 157 } 158 159 nsTableFrame::nsTableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, 160 ClassID aID) 161 : nsContainerFrame(aStyle, aPresContext, aID) { 162 memset(&mBits, 0, sizeof(mBits)); 163 } 164 165 void nsTableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 166 nsIFrame* aPrevInFlow) { 167 MOZ_ASSERT(!mCellMap, "Init called twice"); 168 MOZ_ASSERT(!mTableLayoutStrategy, "Init called twice"); 169 MOZ_ASSERT(!aPrevInFlow || aPrevInFlow->IsTableFrame(), 170 "prev-in-flow must be of same type"); 171 172 // Let the base class do its processing 173 nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 174 175 // see if border collapse is on, if so set it 176 const nsStyleTableBorder* tableStyle = StyleTableBorder(); 177 bool borderCollapse = 178 (StyleBorderCollapse::Collapse == tableStyle->mBorderCollapse); 179 SetBorderCollapse(borderCollapse); 180 if (borderCollapse) { 181 SetNeedToCalcHasBCBorders(true); 182 } 183 184 if (!aPrevInFlow) { 185 // If we're the first-in-flow, we manage the cell map & layout strategy that 186 // get used by our continuation chain: 187 mCellMap = MakeUnique<nsTableCellMap>(*this, borderCollapse); 188 if (IsAutoLayout()) { 189 mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this); 190 } else { 191 mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this); 192 } 193 } else { 194 // Set my isize, because all frames in a table flow are the same isize and 195 // code in nsTableWrapperFrame depends on this being set. 196 WritingMode wm = GetWritingMode(); 197 SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm))); 198 } 199 } 200 201 // Define here (Rather than in the header), even if it's trival, to avoid 202 // UniquePtr members causing compile errors when their destructors are 203 // implicitly inserted into this destructor. Destruction requires 204 // the full definition of types that these UniquePtrs are managing, and 205 // the header only has forward declarations of them. 206 nsTableFrame::~nsTableFrame() = default; 207 208 void nsTableFrame::Destroy(DestroyContext& aContext) { 209 MOZ_ASSERT(!mBits.mIsDestroying); 210 mBits.mIsDestroying = true; 211 mColGroups.DestroyFrames(aContext); 212 nsContainerFrame::Destroy(aContext); 213 } 214 215 static bool IsRepeatedFrame(nsIFrame* kidFrame) { 216 return (kidFrame->IsTableRowFrame() || kidFrame->IsTableRowGroupFrame()) && 217 kidFrame->HasAnyStateBits(NS_REPEATED_ROW_OR_ROWGROUP); 218 } 219 220 bool nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame, 221 nsIFrame* aNextFrame) { 222 const nsStyleDisplay* display = aSourceFrame->StyleDisplay(); 223 nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame); 224 // don't allow a page break after a repeated element ... 225 if ((display->BreakAfter() || (prevRg && prevRg->HasInternalBreakAfter())) && 226 !IsRepeatedFrame(aSourceFrame)) { 227 return !(aNextFrame && IsRepeatedFrame(aNextFrame)); // or before 228 } 229 230 if (aNextFrame) { 231 display = aNextFrame->StyleDisplay(); 232 // don't allow a page break before a repeated element ... 233 nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame); 234 if ((display->BreakBefore() || 235 (nextRg && nextRg->HasInternalBreakBefore())) && 236 !IsRepeatedFrame(aNextFrame)) { 237 return !IsRepeatedFrame(aSourceFrame); // or after 238 } 239 } 240 return false; 241 } 242 243 /* static */ 244 void nsTableFrame::PositionedTablePartMaybeChanged(nsContainerFrame* aFrame, 245 ComputedStyle* aOldStyle) { 246 const bool wasPositioned = 247 aOldStyle && aOldStyle->IsAbsPosContainingBlock(aFrame); 248 const bool isPositioned = aFrame->IsAbsPosContainingBlock(); 249 MOZ_ASSERT(isPositioned == aFrame->Style()->IsAbsPosContainingBlock(aFrame)); 250 if (wasPositioned == isPositioned) { 251 return; 252 } 253 254 nsTableFrame* tableFrame = GetTableFrame(aFrame); 255 MOZ_ASSERT(tableFrame, "Should have a table frame here"); 256 tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation()); 257 258 // Retrieve the positioned parts array for this table. 259 TablePartsArray* positionedParts = 260 tableFrame->GetProperty(PositionedTablePartsProperty()); 261 262 // Lazily create the array if it doesn't exist yet. 263 if (!positionedParts) { 264 positionedParts = new TablePartsArray; 265 tableFrame->SetProperty(PositionedTablePartsProperty(), positionedParts); 266 } 267 268 if (isPositioned) { 269 // Add this frame to the list. 270 positionedParts->AppendElement(aFrame); 271 } else { 272 positionedParts->RemoveElement(aFrame); 273 } 274 } 275 276 /* static */ 277 void nsTableFrame::MaybeUnregisterPositionedTablePart( 278 nsContainerFrame* aFrame) { 279 if (!aFrame->IsAbsPosContainingBlock()) { 280 return; 281 } 282 nsTableFrame* tableFrame = GetTableFrame(aFrame); 283 tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation()); 284 285 if (tableFrame->IsDestroying()) { 286 return; // We're throwing the table away anyways. 287 } 288 289 // Retrieve the positioned parts array for this table. 290 TablePartsArray* positionedParts = 291 tableFrame->GetProperty(PositionedTablePartsProperty()); 292 293 // Remove the frame. 294 MOZ_ASSERT( 295 positionedParts && positionedParts->Contains(aFrame), 296 "Asked to unregister a positioned table part that wasn't registered"); 297 if (positionedParts) { 298 positionedParts->RemoveElement(aFrame); 299 } 300 } 301 302 // XXX this needs to be cleaned up so that the frame constructor breaks out col 303 // group frames into a separate child list, bug 343048. 304 void nsTableFrame::SetInitialChildList(ChildListID aListID, 305 nsFrameList&& aChildList) { 306 if (aListID != FrameChildListID::Principal) { 307 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 308 return; 309 } 310 311 MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(), 312 "unexpected second call to SetInitialChildList"); 313 #ifdef DEBUG 314 for (nsIFrame* f : aChildList) { 315 MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); 316 } 317 #endif 318 319 // XXXbz the below code is an icky cesspit that's only needed in its current 320 // form for two reasons: 321 // 1) Both rowgroups and column groups come in on the principal child list. 322 while (aChildList.NotEmpty()) { 323 nsIFrame* childFrame = aChildList.FirstChild(); 324 aChildList.RemoveFirstChild(); 325 const nsStyleDisplay* childDisplay = childFrame->StyleDisplay(); 326 327 if (mozilla::StyleDisplay::TableColumnGroup == childDisplay->mDisplay) { 328 NS_ASSERTION(childFrame->IsTableColGroupFrame(), 329 "This is not a colgroup"); 330 mColGroups.AppendFrame(nullptr, childFrame); 331 } else { // row groups and unknown frames go on the main list for now 332 mFrames.AppendFrame(nullptr, childFrame); 333 } 334 } 335 336 // If we have a prev-in-flow, then we're a table that has been split and 337 // so don't treat this like an append 338 if (!GetPrevInFlow()) { 339 // process col groups first so that real cols get constructed before 340 // anonymous ones due to cells in rows. 341 InsertColGroups(0, mColGroups); 342 InsertRowGroups(mFrames); 343 // calc collapsing borders 344 if (IsBorderCollapse()) { 345 SetFullBCDamageArea(); 346 } 347 } 348 } 349 350 void nsTableFrame::RowOrColSpanChanged(nsTableCellFrame* aCellFrame) { 351 if (aCellFrame) { 352 nsTableCellMap* cellMap = GetCellMap(); 353 if (cellMap) { 354 // for now just remove the cell from the map and reinsert it 355 uint32_t rowIndex = aCellFrame->RowIndex(); 356 uint32_t colIndex = aCellFrame->ColIndex(); 357 RemoveCell(aCellFrame, rowIndex); 358 AutoTArray<nsTableCellFrame*, 1> cells; 359 cells.AppendElement(aCellFrame); 360 InsertCells(cells, rowIndex, colIndex - 1); 361 362 // XXX Should this use IntrinsicDirty::FrameAncestorsAndDescendants? It 363 // currently doesn't need to, but it might given more optimization. 364 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 365 NS_FRAME_IS_DIRTY); 366 } 367 } 368 } 369 370 /* ****** CellMap methods ******* */ 371 372 /* return the effective col count */ 373 int32_t nsTableFrame::GetEffectiveColCount() const { 374 int32_t colCount = GetColCount(); 375 if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) { 376 nsTableCellMap* cellMap = GetCellMap(); 377 if (!cellMap) { 378 return 0; 379 } 380 // don't count cols at the end that don't have originating cells 381 for (int32_t colIdx = colCount - 1; colIdx >= 0; colIdx--) { 382 if (cellMap->GetNumCellsOriginatingInCol(colIdx) > 0) { 383 break; 384 } 385 colCount--; 386 } 387 } 388 return colCount; 389 } 390 391 int32_t nsTableFrame::GetIndexOfLastRealCol() { 392 int32_t numCols = mColFrames.Length(); 393 if (numCols > 0) { 394 for (int32_t colIdx = numCols - 1; colIdx >= 0; colIdx--) { 395 nsTableColFrame* colFrame = GetColFrame(colIdx); 396 if (colFrame) { 397 if (eColAnonymousCell != colFrame->GetColType()) { 398 return colIdx; 399 } 400 } 401 } 402 } 403 return -1; 404 } 405 406 nsTableColFrame* nsTableFrame::GetColFrame(int32_t aColIndex) const { 407 MOZ_ASSERT(!GetPrevInFlow(), "GetColFrame called on next in flow"); 408 int32_t numCols = mColFrames.Length(); 409 if ((aColIndex >= 0) && (aColIndex < numCols)) { 410 MOZ_ASSERT(mColFrames.ElementAt(aColIndex)); 411 return mColFrames.ElementAt(aColIndex); 412 } else { 413 MOZ_ASSERT_UNREACHABLE("invalid col index"); 414 return nullptr; 415 } 416 } 417 418 int32_t nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex, 419 const nsTableCellFrame& aCell) const { 420 nsTableCellMap* cellMap = GetCellMap(); 421 MOZ_ASSERT(nullptr != cellMap, "bad call, cellMap not yet allocated."); 422 423 return cellMap->GetEffectiveRowSpan(aRowIndex, aCell.ColIndex()); 424 } 425 426 int32_t nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell, 427 nsCellMap* aCellMap) { 428 nsTableCellMap* tableCellMap = GetCellMap(); 429 if (!tableCellMap) ABORT1(1); 430 431 uint32_t colIndex = aCell.ColIndex(); 432 uint32_t rowIndex = aCell.RowIndex(); 433 434 if (aCellMap) { 435 return aCellMap->GetRowSpan(rowIndex, colIndex, true); 436 } 437 return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex); 438 } 439 440 int32_t nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell, 441 nsCellMap* aCellMap) const { 442 nsTableCellMap* tableCellMap = GetCellMap(); 443 if (!tableCellMap) ABORT1(1); 444 445 uint32_t colIndex = aCell.ColIndex(); 446 uint32_t rowIndex = aCell.RowIndex(); 447 448 if (aCellMap) { 449 return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex); 450 } 451 return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex); 452 } 453 454 bool nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const { 455 nsTableCellMap* tableCellMap = GetCellMap(); 456 if (!tableCellMap) ABORT1(1); 457 return tableCellMap->HasMoreThanOneCell(aRowIndex); 458 } 459 460 void nsTableFrame::AdjustRowIndices(int32_t aRowIndex, int32_t aAdjustment) { 461 // Iterate over the row groups and adjust the row indices of all rows 462 // whose index is >= aRowIndex. 463 RowGroupArray rowGroups = OrderedRowGroups(); 464 465 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 466 rowGroups[rgIdx]->AdjustRowIndices(aRowIndex, aAdjustment); 467 } 468 } 469 470 void nsTableFrame::ResetRowIndices( 471 const nsFrameList::Slice& aRowGroupsToExclude) { 472 // Iterate over the row groups and adjust the row indices of all rows 473 // omit the rowgroups that will be inserted later 474 mDeletedRowIndexRanges.clear(); 475 476 RowGroupArray rowGroups = OrderedRowGroups(); 477 478 nsTHashSet<nsTableRowGroupFrame*> excludeRowGroups; 479 for (nsIFrame* excludeRowGroup : aRowGroupsToExclude) { 480 excludeRowGroups.Insert( 481 static_cast<nsTableRowGroupFrame*>(excludeRowGroup)); 482 #ifdef DEBUG 483 { 484 // Check to make sure that the row indices of all rows in excluded row 485 // groups are '0' (i.e. the initial value since they haven't been added 486 // yet) 487 const nsFrameList& rowFrames = excludeRowGroup->PrincipalChildList(); 488 for (nsIFrame* r : rowFrames) { 489 auto* row = static_cast<nsTableRowFrame*>(r); 490 MOZ_ASSERT(row->GetRowIndex() == 0, 491 "exclusions cannot be used for rows that were already added," 492 "because we'd need to process mDeletedRowIndexRanges"); 493 } 494 } 495 #endif 496 } 497 498 int32_t rowIndex = 0; 499 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 500 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 501 if (!excludeRowGroups.Contains(rgFrame)) { 502 const nsFrameList& rowFrames = rgFrame->PrincipalChildList(); 503 for (nsIFrame* r : rowFrames) { 504 if (mozilla::StyleDisplay::TableRow == r->StyleDisplay()->mDisplay) { 505 auto* row = static_cast<nsTableRowFrame*>(r); 506 row->SetRowIndex(rowIndex); 507 rowIndex++; 508 } 509 } 510 } 511 } 512 } 513 514 void nsTableFrame::InsertColGroups(int32_t aStartColIndex, 515 const nsFrameList::Slice& aColGroups) { 516 int32_t colIndex = aStartColIndex; 517 518 // XXX: We cannot use range-based for loop because AddColsToTable() can 519 // destroy the nsTableColGroupFrame in the slice we're traversing! Need to 520 // check the validity of *colGroupIter. 521 auto colGroupIter = aColGroups.begin(); 522 for (auto colGroupIterEnd = aColGroups.end(); 523 *colGroupIter && colGroupIter != colGroupIterEnd; ++colGroupIter) { 524 MOZ_ASSERT((*colGroupIter)->IsTableColGroupFrame()); 525 auto* cgFrame = static_cast<nsTableColGroupFrame*>(*colGroupIter); 526 cgFrame->SetStartColumnIndex(colIndex); 527 cgFrame->AddColsToTable(colIndex, false, cgFrame->PrincipalChildList()); 528 int32_t numCols = cgFrame->GetColCount(); 529 colIndex += numCols; 530 } 531 532 if (*colGroupIter) { 533 nsTableColGroupFrame::ResetColIndices(*colGroupIter, colIndex); 534 } 535 } 536 537 void nsTableFrame::InsertCol(nsTableColFrame& aColFrame, int32_t aColIndex) { 538 mColFrames.InsertElementAt(aColIndex, &aColFrame); 539 nsTableColType insertedColType = aColFrame.GetColType(); 540 int32_t numCacheCols = mColFrames.Length(); 541 nsTableCellMap* cellMap = GetCellMap(); 542 if (cellMap) { 543 int32_t numMapCols = cellMap->GetColCount(); 544 if (numCacheCols > numMapCols) { 545 bool removedFromCache = false; 546 if (eColAnonymousCell != insertedColType) { 547 nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1); 548 if (lastCol) { 549 nsTableColType lastColType = lastCol->GetColType(); 550 if (eColAnonymousCell == lastColType) { 551 // remove the col from the cache 552 mColFrames.RemoveLastElement(); 553 // remove the col from the synthetic col group 554 nsTableColGroupFrame* lastColGroup = 555 (nsTableColGroupFrame*)mColGroups.LastChild(); 556 if (lastColGroup) { 557 MOZ_ASSERT(lastColGroup->IsSynthetic()); 558 DestroyContext context(PresShell()); 559 lastColGroup->RemoveChild(context, *lastCol, false); 560 561 // remove the col group if it is empty 562 if (lastColGroup->GetColCount() <= 0) { 563 mColGroups.DestroyFrame(context, (nsIFrame*)lastColGroup); 564 } 565 } 566 removedFromCache = true; 567 } 568 } 569 } 570 if (!removedFromCache) { 571 cellMap->AddColsAtEnd(1); 572 } 573 } 574 } 575 // for now, just bail and recalc all of the collapsing borders 576 if (IsBorderCollapse()) { 577 TableArea damageArea(aColIndex, 0, GetColCount() - aColIndex, 578 GetRowCount()); 579 AddBCDamageArea(damageArea); 580 } 581 } 582 583 void nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame, 584 int32_t aColIndex, bool aRemoveFromCache, 585 bool aRemoveFromCellMap) { 586 if (aRemoveFromCache) { 587 mColFrames.RemoveElementAt(aColIndex); 588 } 589 if (aRemoveFromCellMap) { 590 nsTableCellMap* cellMap = GetCellMap(); 591 if (cellMap) { 592 // If we have some anonymous cols at the end already, we just 593 // add a new anonymous col. 594 if (!mColFrames.IsEmpty() && 595 mColFrames.LastElement() && // XXXbz is this ever null? 596 mColFrames.LastElement()->GetColType() == eColAnonymousCell) { 597 AppendAnonymousColFrames(1); 598 } else { 599 // All of our colframes correspond to actual <col> tags. It's possible 600 // that we still have at least as many <col> tags as we have logical 601 // columns from cells, but we might have one less. Handle the latter 602 // case as follows: First ask the cellmap to drop its last col if it 603 // doesn't have any actual cells in it. Then call 604 // MatchCellMapToColCache to append an anonymous column if it's needed; 605 // this needs to be after RemoveColsAtEnd, since it will determine the 606 // need for a new column frame based on the width of the cell map. 607 cellMap->RemoveColsAtEnd(); 608 MatchCellMapToColCache(cellMap); 609 } 610 } 611 } 612 // for now, just bail and recalc all of the collapsing borders 613 if (IsBorderCollapse()) { 614 TableArea damageArea(0, 0, GetColCount(), GetRowCount()); 615 AddBCDamageArea(damageArea); 616 } 617 } 618 619 /** Get the cell map for this table frame. It is not always mCellMap. 620 * Only the first-in-flow has a legit cell map. 621 */ 622 nsTableCellMap* nsTableFrame::GetCellMap() const { 623 return static_cast<nsTableFrame*>(FirstInFlow())->mCellMap.get(); 624 } 625 626 nsTableColGroupFrame* nsTableFrame::CreateSyntheticColGroupFrame() { 627 nsIContent* colGroupContent = GetContent(); 628 mozilla::PresShell* presShell = PresShell(); 629 630 RefPtr<ComputedStyle> colGroupStyle; 631 colGroupStyle = presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle( 632 PseudoStyleType::tableColGroup); 633 // Create a col group frame 634 nsTableColGroupFrame* newFrame = 635 NS_NewTableColGroupFrame(presShell, colGroupStyle); 636 newFrame->SetIsSynthetic(); 637 newFrame->Init(colGroupContent, this, nullptr); 638 return newFrame; 639 } 640 641 void nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) { 642 MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_."); 643 // get the last col group frame 644 nsTableColGroupFrame* colGroupFrame = 645 static_cast<nsTableColGroupFrame*>(mColGroups.LastChild()); 646 647 if (!colGroupFrame || !colGroupFrame->IsSynthetic()) { 648 int32_t colIndex = (colGroupFrame) ? colGroupFrame->GetStartColumnIndex() + 649 colGroupFrame->GetColCount() 650 : 0; 651 colGroupFrame = CreateSyntheticColGroupFrame(); 652 if (!colGroupFrame) { 653 return; 654 } 655 // add the new frame to the child list 656 mColGroups.AppendFrame(this, colGroupFrame); 657 colGroupFrame->SetStartColumnIndex(colIndex); 658 } 659 AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell, 660 true); 661 } 662 663 // XXX this needs to be moved to nsCSSFrameConstructor 664 // Right now it only creates the col frames at the end 665 void nsTableFrame::AppendAnonymousColFrames( 666 nsTableColGroupFrame* aColGroupFrame, int32_t aNumColsToAdd, 667 nsTableColType aColType, bool aAddToTable) { 668 MOZ_ASSERT(aColGroupFrame, "null frame"); 669 MOZ_ASSERT(aColType != eColAnonymousCol, "Shouldn't happen"); 670 MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_."); 671 672 mozilla::PresShell* presShell = PresShell(); 673 674 // Get the last col frame 675 nsFrameList newColFrames; 676 677 int32_t startIndex = mColFrames.Length(); 678 int32_t lastIndex = startIndex + aNumColsToAdd - 1; 679 680 for (int32_t childX = startIndex; childX <= lastIndex; childX++) { 681 // all anonymous cols that we create here use a pseudo ComputedStyle of the 682 // col group 683 nsIContent* iContent = aColGroupFrame->GetContent(); 684 RefPtr<ComputedStyle> computedStyle = 685 presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle( 686 PseudoStyleType::tableCol); 687 // ASSERTION to check for bug 54454 sneaking back in... 688 NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames"); 689 690 // create the new col frame 691 nsIFrame* colFrame = NS_NewTableColFrame(presShell, computedStyle); 692 ((nsTableColFrame*)colFrame)->SetColType(aColType); 693 colFrame->Init(iContent, aColGroupFrame, nullptr); 694 695 newColFrames.AppendFrame(nullptr, colFrame); 696 } 697 nsFrameList& cols = aColGroupFrame->GetWritableChildList(); 698 nsIFrame* oldLastCol = cols.LastChild(); 699 const nsFrameList::Slice& newCols = 700 cols.InsertFrames(nullptr, oldLastCol, std::move(newColFrames)); 701 if (aAddToTable) { 702 // get the starting col index in the cache 703 int32_t startColIndex; 704 if (oldLastCol) { 705 startColIndex = 706 static_cast<nsTableColFrame*>(oldLastCol)->GetColIndex() + 1; 707 } else { 708 startColIndex = aColGroupFrame->GetStartColumnIndex(); 709 } 710 711 aColGroupFrame->AddColsToTable(startColIndex, true, newCols); 712 } 713 } 714 715 void nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) { 716 int32_t numColsInMap = GetColCount(); 717 int32_t numColsInCache = mColFrames.Length(); 718 int32_t numColsToAdd = numColsInMap - numColsInCache; 719 if (numColsToAdd > 0) { 720 // this sets the child list, updates the col cache and cell map 721 AppendAnonymousColFrames(numColsToAdd); 722 } 723 if (numColsToAdd < 0) { 724 int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd); 725 // if the cell map has fewer cols than the cache, correct it 726 if (numColsNotRemoved > 0) { 727 aCellMap->AddColsAtEnd(numColsNotRemoved); 728 } 729 } 730 } 731 732 void nsTableFrame::DidResizeColumns() { 733 MOZ_ASSERT(!GetPrevInFlow(), "should only be called on first-in-flow"); 734 735 if (mBits.mResizedColumns) { 736 return; // already marked 737 } 738 739 for (nsTableFrame* f = this; f; 740 f = static_cast<nsTableFrame*>(f->GetNextInFlow())) { 741 f->mBits.mResizedColumns = true; 742 } 743 } 744 745 void nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, int32_t aRowIndex) { 746 nsTableCellMap* cellMap = GetCellMap(); 747 if (cellMap) { 748 TableArea damageArea(0, 0, 0, 0); 749 cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea); 750 MatchCellMapToColCache(cellMap); 751 if (IsBorderCollapse()) { 752 AddBCDamageArea(damageArea); 753 } 754 } 755 } 756 757 void nsTableFrame::InsertCells(nsTArray<nsTableCellFrame*>& aCellFrames, 758 int32_t aRowIndex, int32_t aColIndexBefore) { 759 nsTableCellMap* cellMap = GetCellMap(); 760 if (cellMap) { 761 TableArea damageArea(0, 0, 0, 0); 762 cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore, damageArea); 763 MatchCellMapToColCache(cellMap); 764 if (IsBorderCollapse()) { 765 AddBCDamageArea(damageArea); 766 } 767 } 768 } 769 770 // this removes the frames from the col group and table, but not the cell map 771 int32_t nsTableFrame::DestroyAnonymousColFrames(int32_t aNumFrames) { 772 // only remove cols that are of type eTypeAnonymous cell (they are at the end) 773 int32_t endIndex = mColFrames.Length() - 1; 774 int32_t startIndex = (endIndex - aNumFrames) + 1; 775 int32_t numColsRemoved = 0; 776 DestroyContext context(PresShell()); 777 for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) { 778 nsTableColFrame* colFrame = GetColFrame(colIdx); 779 if (colFrame && (eColAnonymousCell == colFrame->GetColType())) { 780 auto* cgFrame = static_cast<nsTableColGroupFrame*>(colFrame->GetParent()); 781 // remove the frame from the colgroup 782 cgFrame->RemoveChild(context, *colFrame, false); 783 // remove the frame from the cache, but not the cell map 784 RemoveCol(nullptr, colIdx, true, false); 785 numColsRemoved++; 786 } else { 787 break; 788 } 789 } 790 return (aNumFrames - numColsRemoved); 791 } 792 793 void nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, int32_t aRowIndex) { 794 nsTableCellMap* cellMap = GetCellMap(); 795 if (cellMap) { 796 TableArea damageArea(0, 0, 0, 0); 797 cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea); 798 MatchCellMapToColCache(cellMap); 799 if (IsBorderCollapse()) { 800 AddBCDamageArea(damageArea); 801 } 802 } 803 } 804 805 int32_t nsTableFrame::GetStartRowIndex( 806 const nsTableRowGroupFrame* aRowGroupFrame) const { 807 RowGroupArray orderedRowGroups = OrderedRowGroups(); 808 809 int32_t rowIndex = 0; 810 for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { 811 nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; 812 if (rgFrame == aRowGroupFrame) { 813 break; 814 } 815 int32_t numRows = rgFrame->GetRowCount(); 816 rowIndex += numRows; 817 } 818 return rowIndex; 819 } 820 821 // this cannot extend beyond a single row group 822 void nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame, 823 int32_t aRowIndex, 824 nsTArray<nsTableRowFrame*>& aRowFrames) { 825 nsTableCellMap* cellMap = GetCellMap(); 826 if (cellMap) { 827 int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex; 828 InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true); 829 } 830 } 831 832 // this cannot extend beyond a single row group 833 int32_t nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame, 834 nsTArray<nsTableRowFrame*>& aRowFrames, 835 int32_t aRowIndex, bool aConsiderSpans) { 836 #ifdef DEBUG_TABLE_CELLMAP 837 printf("=== insertRowsBefore firstRow=%d \n", aRowIndex); 838 Dump(true, false, true); 839 #endif 840 841 int32_t numColsToAdd = 0; 842 nsTableCellMap* cellMap = GetCellMap(); 843 if (cellMap) { 844 TableArea damageArea(0, 0, 0, 0); 845 bool shouldRecalculateIndex = !IsDeletedRowIndexRangesEmpty(); 846 if (shouldRecalculateIndex) { 847 ResetRowIndices(nsFrameList::Slice(nullptr, nullptr)); 848 } 849 int32_t origNumRows = cellMap->GetRowCount(); 850 int32_t numNewRows = aRowFrames.Length(); 851 cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans, 852 damageArea); 853 MatchCellMapToColCache(cellMap); 854 855 // Perform row index adjustment only if row indices were not 856 // reset above 857 if (!shouldRecalculateIndex) { 858 if (aRowIndex < origNumRows) { 859 AdjustRowIndices(aRowIndex, numNewRows); 860 } 861 862 // assign the correct row indices to the new rows. If they were 863 // recalculated above it may not have been done correctly because each row 864 // is constructed with index 0 865 for (int32_t rowB = 0; rowB < numNewRows; rowB++) { 866 nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowB); 867 rowFrame->SetRowIndex(aRowIndex + rowB); 868 } 869 } 870 871 if (IsBorderCollapse()) { 872 AddBCDamageArea(damageArea); 873 } 874 } 875 #ifdef DEBUG_TABLE_CELLMAP 876 printf("=== insertRowsAfter \n"); 877 Dump(true, false, true); 878 #endif 879 880 return numColsToAdd; 881 } 882 883 void nsTableFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) { 884 if (mDeletedRowIndexRanges.empty()) { 885 mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>( 886 aDeletedRowStoredIndex, aDeletedRowStoredIndex)); 887 return; 888 } 889 890 // Find the position of the current deleted row's stored index 891 // among the previous deleted row index ranges and merge ranges if 892 // they are consecutive, else add a new (disjoint) range to the map. 893 // Call to mDeletedRowIndexRanges.upper_bound is 894 // O(log(mDeletedRowIndexRanges.size())) therefore call to 895 // AddDeletedRowIndex is also ~O(log(mDeletedRowIndexRanges.size())) 896 897 // greaterIter = will point to smallest range in the map with lower value 898 // greater than the aDeletedRowStoredIndex. 899 // If no such value exists, point to end of map. 900 // smallerIter = will point to largest range in the map with higher value 901 // smaller than the aDeletedRowStoredIndex 902 // If no such value exists, point to beginning of map. 903 // i.e. when both values exist below is true: 904 // smallerIter->second < aDeletedRowStoredIndex < greaterIter->first 905 auto greaterIter = mDeletedRowIndexRanges.upper_bound(aDeletedRowStoredIndex); 906 auto smallerIter = greaterIter; 907 908 if (smallerIter != mDeletedRowIndexRanges.begin()) { 909 smallerIter--; 910 // While greaterIter might be out-of-bounds (by being equal to end()), 911 // smallerIter now cannot be, since we returned early above for a 0-size 912 // map. 913 } 914 915 // Note: smallerIter can only be equal to greaterIter when both 916 // of them point to the beginning of the map and in that case smallerIter 917 // does not "exist" but we clip smallerIter to point to beginning of map 918 // so that it doesn't point to something unknown or outside the map boundry. 919 // Note: When greaterIter is not the end (i.e. it "exists") upper_bound() 920 // ensures aDeletedRowStoredIndex < greaterIter->first so no need to 921 // assert that. 922 MOZ_ASSERT(smallerIter == greaterIter || 923 aDeletedRowStoredIndex > smallerIter->second, 924 "aDeletedRowIndexRanges already contains aDeletedRowStoredIndex! " 925 "Trying to delete an already deleted row?"); 926 927 if (smallerIter->second == aDeletedRowStoredIndex - 1) { 928 if (greaterIter != mDeletedRowIndexRanges.end() && 929 greaterIter->first == aDeletedRowStoredIndex + 1) { 930 // merge current index with smaller and greater range as they are 931 // consecutive 932 smallerIter->second = greaterIter->second; 933 mDeletedRowIndexRanges.erase(greaterIter); 934 } else { 935 // add aDeletedRowStoredIndex in the smaller range as it is consecutive 936 smallerIter->second = aDeletedRowStoredIndex; 937 } 938 } else if (greaterIter != mDeletedRowIndexRanges.end() && 939 greaterIter->first == aDeletedRowStoredIndex + 1) { 940 // add aDeletedRowStoredIndex in the greater range as it is consecutive 941 mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>( 942 aDeletedRowStoredIndex, greaterIter->second)); 943 mDeletedRowIndexRanges.erase(greaterIter); 944 } else { 945 // add new range as aDeletedRowStoredIndex is disjoint from existing ranges 946 mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>( 947 aDeletedRowStoredIndex, aDeletedRowStoredIndex)); 948 } 949 } 950 951 int32_t nsTableFrame::GetAdjustmentForStoredIndex(int32_t aStoredIndex) { 952 if (mDeletedRowIndexRanges.empty()) { 953 return 0; 954 } 955 956 int32_t adjustment = 0; 957 958 // O(log(mDeletedRowIndexRanges.size())) 959 auto endIter = mDeletedRowIndexRanges.upper_bound(aStoredIndex); 960 for (auto iter = mDeletedRowIndexRanges.begin(); iter != endIter; ++iter) { 961 adjustment += iter->second - iter->first + 1; 962 } 963 964 return adjustment; 965 } 966 967 // this cannot extend beyond a single row group 968 void nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame, 969 int32_t aNumRowsToRemove, bool aConsiderSpans) { 970 #ifdef TBD_OPTIMIZATION 971 // decide if we need to rebalance. we have to do this here because the row 972 // group cannot do it when it gets the dirty reflow corresponding to the frame 973 // being destroyed 974 bool stopTelling = false; 975 for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking); 976 kidFrame = kidFrame->GetNextSibling()) { 977 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame); 978 if (cellFrame) { 979 stopTelling = tableFrame->CellChangedWidth( 980 *cellFrame, cellFrame->GetPass1MaxElementWidth(), 981 cellFrame->GetMaximumWidth(), true); 982 } 983 } 984 // XXX need to consider what happens if there are cells that have rowspans 985 // into the deleted row. Need to consider moving rows if a rebalance doesn't 986 // happen 987 #endif 988 989 int32_t firstRowIndex = aFirstRowFrame.GetRowIndex(); 990 #ifdef DEBUG_TABLE_CELLMAP 991 printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex, 992 aNumRowsToRemove); 993 Dump(true, false, true); 994 #endif 995 nsTableCellMap* cellMap = GetCellMap(); 996 if (cellMap) { 997 TableArea damageArea(0, 0, 0, 0); 998 999 // Mark rows starting from aFirstRowFrame to the next 'aNumRowsToRemove-1' 1000 // number of rows as deleted. 1001 nsTableRowGroupFrame* parentFrame = aFirstRowFrame.GetTableRowGroupFrame(); 1002 parentFrame->MarkRowsAsDeleted(aFirstRowFrame, aNumRowsToRemove); 1003 1004 cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans, 1005 damageArea); 1006 MatchCellMapToColCache(cellMap); 1007 if (IsBorderCollapse()) { 1008 AddBCDamageArea(damageArea); 1009 } 1010 } 1011 1012 #ifdef DEBUG_TABLE_CELLMAP 1013 printf("=== removeRowsAfter\n"); 1014 Dump(true, true, true); 1015 #endif 1016 } 1017 1018 // collect the rows ancestors of aFrame 1019 int32_t nsTableFrame::CollectRows(nsIFrame* aFrame, 1020 nsTArray<nsTableRowFrame*>& aCollection) { 1021 MOZ_ASSERT(aFrame, "null frame"); 1022 int32_t numRows = 0; 1023 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { 1024 aCollection.AppendElement(static_cast<nsTableRowFrame*>(childFrame)); 1025 numRows++; 1026 } 1027 return numRows; 1028 } 1029 1030 void nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) { 1031 #ifdef DEBUG_TABLE_CELLMAP 1032 printf("=== insertRowGroupsBefore\n"); 1033 Dump(true, false, true); 1034 #endif 1035 nsTableCellMap* cellMap = GetCellMap(); 1036 if (cellMap) { 1037 RowGroupArray orderedRowGroups = OrderedRowGroups(); 1038 1039 AutoTArray<nsTableRowFrame*, 8> rows; 1040 // Loop over the rowgroups and check if some of them are new, if they are 1041 // insert cellmaps in the order that is predefined by OrderedRowGroups. 1042 // XXXbz this code is O(N*M) where N is number of new rowgroups 1043 // and M is number of rowgroups we have! 1044 uint32_t rgIndex; 1045 for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { 1046 for (nsIFrame* rowGroup : aRowGroups) { 1047 if (orderedRowGroups[rgIndex] == rowGroup) { 1048 nsTableRowGroupFrame* priorRG = 1049 (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; 1050 // create and add the cell map for the row group 1051 cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG); 1052 1053 break; 1054 } 1055 } 1056 } 1057 cellMap->Synchronize(this); 1058 ResetRowIndices(aRowGroups); 1059 1060 // now that the cellmaps are reordered too insert the rows 1061 for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { 1062 for (nsIFrame* rowGroup : aRowGroups) { 1063 if (orderedRowGroups[rgIndex] == rowGroup) { 1064 nsTableRowGroupFrame* priorRG = 1065 (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; 1066 // collect the new row frames in an array and add them to the table 1067 int32_t numRows = CollectRows(rowGroup, rows); 1068 if (numRows > 0) { 1069 int32_t rowIndex = 0; 1070 if (priorRG) { 1071 int32_t priorNumRows = priorRG->GetRowCount(); 1072 rowIndex = priorRG->GetStartRowIndex() + priorNumRows; 1073 } 1074 InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true); 1075 rows.Clear(); 1076 } 1077 break; 1078 } 1079 } 1080 } 1081 } 1082 #ifdef DEBUG_TABLE_CELLMAP 1083 printf("=== insertRowGroupsAfter\n"); 1084 Dump(true, true, true); 1085 #endif 1086 } 1087 1088 ///////////////////////////////////////////////////////////////////////////// 1089 // Child frame enumeration 1090 1091 const nsFrameList& nsTableFrame::GetChildList(ChildListID aListID) const { 1092 if (aListID == FrameChildListID::ColGroup) { 1093 return mColGroups; 1094 } 1095 return nsContainerFrame::GetChildList(aListID); 1096 } 1097 1098 void nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const { 1099 nsContainerFrame::GetChildLists(aLists); 1100 mColGroups.AppendIfNonempty(aLists, FrameChildListID::ColGroup); 1101 } 1102 1103 static inline bool FrameHasBorder(nsIFrame* f) { 1104 if (!f->StyleVisibility()->IsVisible()) { 1105 return false; 1106 } 1107 1108 return f->StyleBorder()->HasBorder(); 1109 } 1110 1111 void nsTableFrame::CalcHasBCBorders() { 1112 if (!IsBorderCollapse()) { 1113 SetHasBCBorders(false); 1114 return; 1115 } 1116 1117 if (FrameHasBorder(this)) { 1118 SetHasBCBorders(true); 1119 return; 1120 } 1121 1122 // Check col and col group has borders. 1123 for (nsIFrame* f : this->GetChildList(FrameChildListID::ColGroup)) { 1124 if (FrameHasBorder(f)) { 1125 SetHasBCBorders(true); 1126 return; 1127 } 1128 1129 nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(f); 1130 for (nsTableColFrame* col = colGroup->GetFirstColumn(); col; 1131 col = col->GetNextCol()) { 1132 if (FrameHasBorder(col)) { 1133 SetHasBCBorders(true); 1134 return; 1135 } 1136 } 1137 } 1138 1139 // check row group, row and cell has borders. 1140 RowGroupArray rowGroups = OrderedRowGroups(); 1141 for (nsTableRowGroupFrame* rowGroup : rowGroups) { 1142 if (FrameHasBorder(rowGroup)) { 1143 SetHasBCBorders(true); 1144 return; 1145 } 1146 1147 for (nsTableRowFrame* row = rowGroup->GetFirstRow(); row; 1148 row = row->GetNextRow()) { 1149 if (FrameHasBorder(row)) { 1150 SetHasBCBorders(true); 1151 return; 1152 } 1153 1154 for (nsTableCellFrame* cell = row->GetFirstCell(); cell; 1155 cell = cell->GetNextCell()) { 1156 if (FrameHasBorder(cell)) { 1157 SetHasBCBorders(true); 1158 return; 1159 } 1160 } 1161 } 1162 } 1163 1164 SetHasBCBorders(false); 1165 } 1166 1167 namespace mozilla { 1168 class nsDisplayTableBorderCollapse; 1169 } 1170 1171 // table paint code is concerned primarily with borders and bg color 1172 // SEC: TODO: adjust the rect for captions 1173 void nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1174 const nsDisplayListSet& aLists) { 1175 DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255, 128, 255)); 1176 1177 DisplayBorderBackgroundOutline(aBuilder, aLists); 1178 1179 nsDisplayTableBackgroundSet tableBGs(aBuilder, this); 1180 nsDisplayListCollection lists(aBuilder); 1181 1182 // This is similar to what 1183 // nsContainerFrame::BuildDisplayListForNonBlockChildren does, except that we 1184 // allow the children's background and borders to go in our BorderBackground 1185 // list. This doesn't really affect background painting --- the children won't 1186 // actually draw their own backgrounds because the nsTableFrame already drew 1187 // them, unless a child has its own stacking context, in which case the child 1188 // won't use its passed-in BorderBackground list anyway. It does affect cell 1189 // borders though; this lets us get cell borders into the nsTableFrame's 1190 // BorderBackground list. 1191 for (nsIFrame* colGroup : 1192 FirstContinuation()->GetChildList(FrameChildListID::ColGroup)) { 1193 for (nsIFrame* col : colGroup->PrincipalChildList()) { 1194 tableBGs.AddColumn((nsTableColFrame*)col); 1195 } 1196 } 1197 1198 if (!mFrames.IsEmpty() && !HidesContent()) { 1199 for (nsIFrame* kid : mFrames) { 1200 BuildDisplayListForChild(aBuilder, kid, lists); 1201 } 1202 } 1203 1204 tableBGs.MoveTo(aLists); 1205 lists.MoveTo(aLists); 1206 1207 if (IsVisibleForPainting()) { 1208 // In the collapsed border model, overlay all collapsed borders. 1209 if (IsBorderCollapse()) { 1210 if (HasBCBorders()) { 1211 aLists.BorderBackground()->AppendNewToTop<nsDisplayTableBorderCollapse>( 1212 aBuilder, this); 1213 } 1214 } else { 1215 const nsStyleBorder* borderStyle = StyleBorder(); 1216 if (borderStyle->HasBorder()) { 1217 aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, 1218 this); 1219 } 1220 } 1221 } 1222 } 1223 1224 LogicalSides nsTableFrame::GetLogicalSkipSides() const { 1225 LogicalSides skip(mWritingMode); 1226 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == 1227 StyleBoxDecorationBreak::Clone)) { 1228 return skip; 1229 } 1230 1231 // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto 1232 // account for pagination 1233 if (GetPrevInFlow()) { 1234 skip += LogicalSide::BStart; 1235 } 1236 if (GetNextInFlow()) { 1237 skip += LogicalSide::BEnd; 1238 } 1239 return skip; 1240 } 1241 1242 void nsTableFrame::SetColumnDimensions(nscoord aBSize, WritingMode aWM, 1243 const LogicalMargin& aBorderPadding, 1244 const nsSize& aContainerSize) { 1245 const nscoord colBSize = 1246 aBSize - (aBorderPadding.BStartEnd(aWM) + GetRowSpacing(-1) + 1247 GetRowSpacing(GetRowCount())); 1248 int32_t colIdx = 0; 1249 LogicalPoint colGroupOrigin(aWM, 1250 aBorderPadding.IStart(aWM) + GetColSpacing(-1), 1251 aBorderPadding.BStart(aWM) + GetRowSpacing(-1)); 1252 nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow()); 1253 for (nsIFrame* colGroupFrame : mColGroups) { 1254 MOZ_ASSERT(colGroupFrame->IsTableColGroupFrame()); 1255 // first we need to figure out the size of the colgroup 1256 int32_t groupFirstCol = colIdx; 1257 nscoord colGroupISize = 0; 1258 nscoord colSpacing = 0; 1259 const nsFrameList& columnList = colGroupFrame->PrincipalChildList(); 1260 for (nsIFrame* colFrame : columnList) { 1261 if (mozilla::StyleDisplay::TableColumn == 1262 colFrame->StyleDisplay()->mDisplay) { 1263 NS_ASSERTION(colIdx < GetColCount(), "invalid number of columns"); 1264 colSpacing = GetColSpacing(colIdx); 1265 colGroupISize += 1266 fif->GetColumnISizeFromFirstInFlow(colIdx) + colSpacing; 1267 ++colIdx; 1268 } 1269 } 1270 if (colGroupISize) { 1271 colGroupISize -= colSpacing; 1272 } 1273 1274 LogicalRect colGroupRect(aWM, colGroupOrigin.I(aWM), colGroupOrigin.B(aWM), 1275 colGroupISize, colBSize); 1276 colGroupFrame->SetRect(aWM, colGroupRect, aContainerSize); 1277 nsSize colGroupSize = colGroupFrame->GetSize(); 1278 1279 // then we can place the columns correctly within the group 1280 colIdx = groupFirstCol; 1281 LogicalPoint colOrigin(aWM); 1282 for (nsIFrame* colFrame : columnList) { 1283 if (mozilla::StyleDisplay::TableColumn == 1284 colFrame->StyleDisplay()->mDisplay) { 1285 nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx); 1286 LogicalRect colRect(aWM, colOrigin.I(aWM), colOrigin.B(aWM), colISize, 1287 colBSize); 1288 colFrame->SetRect(aWM, colRect, colGroupSize); 1289 colSpacing = GetColSpacing(colIdx); 1290 colOrigin.I(aWM) += colISize + colSpacing; 1291 ++colIdx; 1292 } 1293 } 1294 1295 colGroupOrigin.I(aWM) += colGroupISize + colSpacing; 1296 } 1297 } 1298 1299 // SEC: TODO need to worry about continuing frames prev/next in flow for 1300 // splitting across pages. 1301 1302 // XXX this could be made more general to handle row modifications that change 1303 // the table bsize, but first we need to scrutinize every Invalidate 1304 void nsTableFrame::ProcessRowInserted(nscoord aNewBSize) { 1305 SetRowInserted(false); // reset the bit that got us here 1306 RowGroupArray rowGroups = OrderedRowGroups(); 1307 // find the row group containing the inserted row 1308 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 1309 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 1310 NS_ASSERTION(rgFrame, "Must have rgFrame here"); 1311 // find the row that was inserted first 1312 for (nsIFrame* childFrame : rgFrame->PrincipalChildList()) { 1313 nsTableRowFrame* rowFrame = do_QueryFrame(childFrame); 1314 if (rowFrame) { 1315 if (rowFrame->IsFirstInserted()) { 1316 rowFrame->SetFirstInserted(false); 1317 // damage the table from the 1st row inserted to the end of the table 1318 nsIFrame::InvalidateFrame(); 1319 // XXXbz didn't we do this up front? Why do we need to do it again? 1320 SetRowInserted(false); 1321 return; // found it, so leave 1322 } 1323 } 1324 } 1325 } 1326 } 1327 1328 /* virtual */ 1329 void nsTableFrame::MarkIntrinsicISizesDirty() { 1330 nsITableLayoutStrategy* tls = LayoutStrategy(); 1331 if (MOZ_UNLIKELY(!tls)) { 1332 // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame() 1333 // walking up the ancestor chain in a table next-in-flow. In this case 1334 // our original first-in-flow (which owns the TableLayoutStrategy) has 1335 // already been destroyed and unhooked from the flow chain and thusly 1336 // LayoutStrategy() returns null. All the frames in the flow will be 1337 // destroyed so no need to mark anything dirty here. See bug 595758. 1338 return; 1339 } 1340 tls->MarkIntrinsicISizesDirty(); 1341 1342 // XXXldb Call SetBCDamageArea? 1343 1344 nsContainerFrame::MarkIntrinsicISizesDirty(); 1345 } 1346 1347 nscoord nsTableFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 1348 IntrinsicISizeType aType) { 1349 if (NeedToCalcBCBorders()) { 1350 CalcBCBorders(); 1351 } 1352 1353 ReflowColGroups(aInput.mContext); 1354 1355 return aType == IntrinsicISizeType::MinISize 1356 ? LayoutStrategy()->GetMinISize(aInput.mContext) 1357 : LayoutStrategy()->GetPrefISize(aInput.mContext, false); 1358 } 1359 1360 /* virtual */ nsIFrame::IntrinsicSizeOffsetData 1361 nsTableFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) { 1362 IntrinsicSizeOffsetData result = 1363 nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis); 1364 1365 result.margin = 0; 1366 1367 if (IsBorderCollapse()) { 1368 result.padding = 0; 1369 1370 WritingMode wm = GetWritingMode(); 1371 LogicalMargin outerBC = GetOuterBCBorder(wm); 1372 result.border = outerBC.IStartEnd(wm); 1373 } 1374 1375 return result; 1376 } 1377 1378 /* virtual */ 1379 nsIFrame::SizeComputationResult nsTableFrame::ComputeSize( 1380 const SizeComputationInput& aSizingInput, WritingMode aWM, 1381 const LogicalSize& aCBSize, nscoord aAvailableISize, 1382 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 1383 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 1384 // Only table wrapper calls this method, and it should use our writing mode. 1385 MOZ_ASSERT(aWM == GetWritingMode(), 1386 "aWM should be the same as our writing mode!"); 1387 1388 auto result = nsContainerFrame::ComputeSize( 1389 aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding, 1390 aSizeOverrides, aFlags); 1391 1392 // If our containing block wants to override inner table frame's inline-size 1393 // (e.g. when resolving flex base size), don't enforce the min inline-size 1394 // later in this method. 1395 if (aSizeOverrides.mApplyOverridesVerbatim && aSizeOverrides.mStyleISize && 1396 aSizeOverrides.mStyleISize->IsLengthPercentage()) { 1397 return result; 1398 } 1399 1400 // If we're a container for font size inflation, then shrink 1401 // wrapping inside of us should not apply font size inflation. 1402 AutoMaybeDisableFontInflation an(this); 1403 1404 // Tables never shrink below their min inline-size. 1405 const IntrinsicSizeInput input(aSizingInput.mRenderingContext, Some(aCBSize), 1406 Nothing()); 1407 nscoord minISize = GetMinISize(input); 1408 if (minISize > result.mLogicalSize.ISize(aWM)) { 1409 result.mLogicalSize.ISize(aWM) = minISize; 1410 } 1411 1412 return result; 1413 } 1414 1415 nscoord nsTableFrame::TableShrinkISizeToFit(gfxContext* aRenderingContext, 1416 nscoord aISizeInCB) { 1417 // If we're a container for font size inflation, then shrink 1418 // wrapping inside of us should not apply font size inflation. 1419 AutoMaybeDisableFontInflation an(this); 1420 1421 nscoord result; 1422 const IntrinsicSizeInput input(aRenderingContext, Nothing(), Nothing()); 1423 nscoord minISize = GetMinISize(input); 1424 if (minISize > aISizeInCB) { 1425 result = minISize; 1426 } else { 1427 // Tables shrink inline-size to fit with a slightly different algorithm 1428 // from the one they use for their intrinsic isize (the difference 1429 // relates to handling of percentage isizes on columns). So this 1430 // function differs from nsIFrame::ShrinkISizeToFit by only the 1431 // following line. 1432 // Since we've already called GetMinISize, we don't need to do any 1433 // of the other stuff GetPrefISize does. 1434 nscoord prefISize = LayoutStrategy()->GetPrefISize(aRenderingContext, true); 1435 if (prefISize > aISizeInCB) { 1436 result = aISizeInCB; 1437 } else { 1438 result = prefISize; 1439 } 1440 } 1441 return result; 1442 } 1443 1444 /* virtual */ 1445 LogicalSize nsTableFrame::ComputeAutoSize( 1446 const SizeComputationInput& aSizingInput, WritingMode aWM, 1447 const LogicalSize& aCBSize, nscoord aAvailableISize, 1448 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 1449 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 1450 // Tables always shrink-wrap. 1451 nscoord cbBased = 1452 aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM); 1453 return LogicalSize( 1454 aWM, TableShrinkISizeToFit(aSizingInput.mRenderingContext, cbBased), 1455 NS_UNCONSTRAINEDSIZE); 1456 } 1457 1458 // Return true if aParentReflowInput.frame or any of its ancestors within 1459 // the containing table have non-auto bsize. (e.g. pct or fixed bsize) 1460 bool nsTableFrame::AncestorsHaveStyleBSize( 1461 const ReflowInput& aParentReflowInput) { 1462 WritingMode wm = aParentReflowInput.GetWritingMode(); 1463 for (const ReflowInput* rs = &aParentReflowInput; rs && rs->mFrame; 1464 rs = rs->mParentReflowInput) { 1465 LayoutFrameType frameType = rs->mFrame->Type(); 1466 if (LayoutFrameType::TableCell == frameType || 1467 LayoutFrameType::TableRow == frameType || 1468 LayoutFrameType::TableRowGroup == frameType) { 1469 const auto bsize = 1470 rs->mStylePosition->BSize(wm, AnchorPosResolutionParams::From(rs)); 1471 // calc() with both lengths and percentages treated like 'auto' on 1472 // internal table elements 1473 if (!bsize->IsAuto() && !bsize->HasLengthAndPercentage()) { 1474 return true; 1475 } 1476 } else if (LayoutFrameType::Table == frameType) { 1477 // we reached the containing table, so always return 1478 return !rs->mStylePosition->BSize(wm, AnchorPosResolutionParams::From(rs)) 1479 ->IsAuto(); 1480 } 1481 } 1482 return false; 1483 } 1484 1485 // See if a special block-size reflow needs to occur and if so, 1486 // call RequestSpecialBSizeReflow 1487 void nsTableFrame::CheckRequestSpecialBSizeReflow( 1488 const ReflowInput& aReflowInput) { 1489 NS_ASSERTION(aReflowInput.mFrame->IsTableCellFrame() || 1490 aReflowInput.mFrame->IsTableRowFrame() || 1491 aReflowInput.mFrame->IsTableRowGroupFrame() || 1492 aReflowInput.mFrame->IsTableFrame(), 1493 "unexpected frame type"); 1494 WritingMode wm = aReflowInput.GetWritingMode(); 1495 if (!aReflowInput.mFrame->GetPrevInFlow() && // 1st in flow 1496 (NS_UNCONSTRAINEDSIZE == 1497 aReflowInput.ComputedBSize() || // no computed bsize 1498 0 == aReflowInput.ComputedBSize()) && 1499 aReflowInput.mStylePosition 1500 ->BSize(wm, AnchorPosResolutionParams::From(&aReflowInput)) 1501 ->ConvertsToPercentage() && // pct bsize 1502 nsTableFrame::AncestorsHaveStyleBSize(*aReflowInput.mParentReflowInput)) { 1503 nsTableFrame::RequestSpecialBSizeReflow(aReflowInput); 1504 } 1505 } 1506 1507 // Notify the frame and its ancestors (up to the containing table) that a 1508 // special bsize reflow will occur. During a special bsize reflow, a table, row 1509 // group, row, or cell returns the last size it was reflowed at. However, the 1510 // table may change the bsize of row groups, rows, cells in 1511 // DistributeBSizeToRows after. And the row group can change the bsize of rows, 1512 // cells in CalculateRowBSizes. 1513 void nsTableFrame::RequestSpecialBSizeReflow(const ReflowInput& aReflowInput) { 1514 // notify the frame and its ancestors of the special reflow, stopping at the 1515 // containing table 1516 for (const ReflowInput* rs = &aReflowInput; rs && rs->mFrame; 1517 rs = rs->mParentReflowInput) { 1518 LayoutFrameType frameType = rs->mFrame->Type(); 1519 NS_ASSERTION(LayoutFrameType::TableCell == frameType || 1520 LayoutFrameType::TableRow == frameType || 1521 LayoutFrameType::TableRowGroup == frameType || 1522 LayoutFrameType::Table == frameType, 1523 "unexpected frame type"); 1524 1525 rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); 1526 if (LayoutFrameType::Table == frameType) { 1527 NS_ASSERTION(rs != &aReflowInput, 1528 "should not request special bsize reflow for table"); 1529 // always stop when we reach a table 1530 break; 1531 } 1532 } 1533 } 1534 1535 /****************************************************************************************** 1536 * Before reflow, intrinsic inline-size calculation is done using GetMinISize 1537 * and GetPrefISize. This used to be known as pass 1 reflow. 1538 * 1539 * After the intrinsic isize calculation, the table determines the 1540 * column widths using BalanceColumnISizes() and 1541 * then reflows each child again with a constrained avail isize. This reflow is 1542 * referred to as the pass 2 reflow. 1543 * 1544 * A special bsize reflow (pass 3 reflow) can occur during an initial or resize 1545 * reflow if (a) a row group, row, cell, or a frame inside a cell has a percent 1546 * bsize but no computed bsize or (b) in paginated mode, a table has a bsize. 1547 * (a) supports percent nested tables contained inside cells whose bsizes aren't 1548 * known until after the pass 2 reflow. (b) is necessary because the table 1549 * cannot split until after the pass 2 reflow. The mechanics of the special 1550 * bsize reflow (variety a) are as follows: 1551 * 1552 * 1) Each table related frame (table, row group, row, cell) implements 1553 * NeedsSpecialReflow() to indicate that it should get the reflow. It does 1554 * this when it has a percent bsize but no computed bsize by calling 1555 * CheckRequestSpecialBSizeReflow(). This method calls 1556 * RequestSpecialBSizeReflow() which calls SetNeedSpecialReflow() on its 1557 * ancestors until it reaches the containing table and calls 1558 * SetNeedToInitiateSpecialReflow() on it. For percent bsize frames inside 1559 * cells, during DidReflow(), the cell's NotifyPercentBSize() is called 1560 * (the cell is the reflow input's mPercentBSizeObserver in this case). 1561 * NotifyPercentBSize() calls RequestSpecialBSizeReflow(). 1562 * 1563 * XXX (jfkthame) This comment appears to be out of date; it refers to 1564 * methods/flags that are no longer present in the code. 1565 * 1566 * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true) 1567 * was called, it will do the special bsize reflow, setting the reflow 1568 * input's mFlags.mSpecialBSizeReflow to true and mSpecialHeightInitiator to 1569 * itself. It won't do this if IsPrematureSpecialHeightReflow() returns true 1570 * because in that case another special bsize reflow will be coming along 1571 * with the containing table as the mSpecialHeightInitiator. It is only 1572 * relevant to do the reflow when the mSpecialHeightInitiator is the 1573 * containing table, because if it is a remote ancestor, then appropriate 1574 * bsizes will not be known. 1575 * 1576 * 3) Since the bsizes of the table, row groups, rows, and cells was determined 1577 * during the pass 2 reflow, they return their last desired sizes during the 1578 * special bsize reflow. The reflow only permits percent bsize frames inside 1579 * the cells to resize based on the cells bsize and that bsize was 1580 * determined during the pass 2 reflow. 1581 * 1582 * So, in the case of deeply nested tables, all of the tables that were told to 1583 * initiate a special reflow will do so, but if a table is already in a special 1584 * reflow, it won't inititate the reflow until the current initiator is its 1585 * containing table. Since these reflows are only received by frames that need 1586 * them and they don't cause any rebalancing of tables, the extra overhead is 1587 * minimal. 1588 * 1589 * The type of special reflow that occurs during printing (variety b) follows 1590 * the same mechanism except that all frames will receive the reflow even if 1591 * they don't really need them. 1592 * 1593 * Open issues with the special bsize reflow: 1594 * 1595 * 1) At some point there should be 2 kinds of special bsize reflows because (a) 1596 * and (b) above are really quite different. This would avoid unnecessary 1597 * reflows during printing. 1598 * 1599 * 2) When a cell contains frames whose percent bsizes > 100%, there is data 1600 * loss (see bug 115245). However, this can also occur if a cell has a fixed 1601 * bsize and there is no special bsize reflow. 1602 * 1603 * XXXldb Special bsize reflow should really be its own method, not 1604 * part of nsIFrame::Reflow. It should then call nsIFrame::Reflow on 1605 * the contents of the cells to do the necessary block-axis resizing. 1606 * 1607 ******************************************************************************************/ 1608 1609 /* Layout the entire inner table. */ 1610 void nsTableFrame::Reflow(nsPresContext* aPresContext, 1611 ReflowOutput& aDesiredSize, 1612 const ReflowInput& aReflowInput, 1613 nsReflowStatus& aStatus) { 1614 MarkInReflow(); 1615 DO_GLOBAL_REFLOW_COUNT("nsTableFrame"); 1616 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1617 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), 1618 "The nsTableWrapperFrame should be the out-of-flow if needed"); 1619 1620 const WritingMode wm = aReflowInput.GetWritingMode(); 1621 MOZ_ASSERT(aReflowInput.ComputedLogicalMargin(wm).IsAllZero(), 1622 "Only nsTableWrapperFrame can have margins!"); 1623 1624 bool isPaginated = aPresContext->IsPaginated(); 1625 1626 if (!GetPrevInFlow() && !mTableLayoutStrategy) { 1627 NS_ERROR("strategy should have been created in Init"); 1628 return; 1629 } 1630 1631 // see if collapsing borders need to be calculated 1632 if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) { 1633 CalcBCBorders(); 1634 } 1635 1636 // Check for an overflow list, and append any row group frames being pushed 1637 MoveOverflowToChildList(); 1638 1639 bool haveCalledCalcDesiredBSize = false; 1640 SetHaveReflowedColGroups(false); 1641 1642 LogicalMargin borderPadding = 1643 aReflowInput.ComputedLogicalBorderPadding(wm).ApplySkipSides( 1644 PreReflowBlockLevelLogicalSkipSides()); 1645 nsIFrame* lastChildReflowed = nullptr; 1646 const nsSize containerSize = 1647 aReflowInput.ComputedSizeAsContainerIfConstrained(); 1648 1649 // The tentative width is the width we assumed for the table when the child 1650 // frames were positioned (which only matters in vertical-rl mode, because 1651 // they're positioned relative to the right-hand edge). Then, after reflowing 1652 // the kids, we can check whether the table ends up with a different width 1653 // than this tentative value (either because it was unconstrained, so we used 1654 // zero, or because it was enlarged by the child frames), we make the 1655 // necessary positioning adjustments along the x-axis. 1656 nscoord tentativeContainerWidth = 0; 1657 bool mayAdjustXForAllChildren = false; 1658 1659 // Reflow the entire table (pass 2 and possibly pass 3). This phase is 1660 // necessary during a constrained initial reflow and other reflows which 1661 // require either a strategy init or balance. This isn't done during an 1662 // unconstrained reflow, because it will occur later when the parent reflows 1663 // with a constrained isize. 1664 if (IsSubtreeDirty() || aReflowInput.ShouldReflowAllKids() || 1665 IsGeometryDirty() || isPaginated || aReflowInput.IsBResize() || 1666 NeedToCollapse()) { 1667 if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE || 1668 // Also check IsBResize(), to handle the first Reflow preceding a 1669 // special bsize Reflow, when we've already had a special bsize 1670 // Reflow (where ComputedBSize() would not be 1671 // NS_UNCONSTRAINEDSIZE, but without a style change in between). 1672 aReflowInput.IsBResize()) { 1673 // XXX Eventually, we should modify DistributeBSizeToRows to use 1674 // nsTableRowFrame::GetInitialBSize instead of nsIFrame::BSize(). 1675 // That way, it will make its calculations based on internal table 1676 // frame bsizes as they are before they ever had any extra bsize 1677 // distributed to them. In the meantime, this reflows all the 1678 // internal table frames, which restores them to their state before 1679 // DistributeBSizeToRows was called. 1680 SetGeometryDirty(); 1681 } 1682 1683 bool needToInitiateSpecialReflow = false; 1684 if (isPaginated) { 1685 // see if an extra reflow will be necessary in pagination mode 1686 // when there is a specified table bsize 1687 if (!GetPrevInFlow() && 1688 NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize()) { 1689 nscoord tableSpecifiedBSize = CalcBorderBoxBSize( 1690 aReflowInput, borderPadding, NS_UNCONSTRAINEDSIZE); 1691 if (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE && 1692 tableSpecifiedBSize > 0) { 1693 needToInitiateSpecialReflow = true; 1694 } 1695 } 1696 } else { 1697 needToInitiateSpecialReflow = 1698 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); 1699 } 1700 1701 NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow, 1702 "Shouldn't be in special bsize reflow here!"); 1703 1704 const TableReflowMode firstReflowMode = needToInitiateSpecialReflow 1705 ? TableReflowMode::Measuring 1706 : TableReflowMode::Final; 1707 ReflowTable(aDesiredSize, aReflowInput, borderPadding, firstReflowMode, 1708 lastChildReflowed, aStatus); 1709 1710 // When in vertical-rl mode, there may be two kinds of scenarios in which 1711 // the positioning of all the children need to be adjusted along the x-axis 1712 // because the width we assumed for the table when the child frames were 1713 // being positioned(i.e. tentative width) may be different from the final 1714 // width for the table: 1715 // 1. If the computed width for the table is unconstrained, a dummy zero 1716 // width was assumed as the tentative width to begin with. 1717 // 2. If the child frames enlarge the width for the table, the final width 1718 // becomes larger than the tentative one. 1719 // Let's record the tentative width here, if later the final width turns out 1720 // to be different from this tentative one, it means one of the above 1721 // scenarios happens, then we adjust positioning of all the children. 1722 // Note that vertical-lr, unlike vertical-rl, doesn't need to take special 1723 // care of this situation, because they're positioned relative to the 1724 // left-hand edge. 1725 if (wm.IsVerticalRL()) { 1726 tentativeContainerWidth = containerSize.width; 1727 mayAdjustXForAllChildren = true; 1728 } 1729 1730 // reevaluate special bsize reflow conditions 1731 if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { 1732 needToInitiateSpecialReflow = true; 1733 } 1734 1735 // XXXldb Are all these conditions correct? 1736 if (needToInitiateSpecialReflow && aStatus.IsComplete()) { 1737 // XXXldb Do we need to set the IsBResize flag on any reflow inputs? 1738 1739 ReflowInput& mutable_rs = const_cast<ReflowInput&>(aReflowInput); 1740 1741 // distribute extra block-direction space to rows 1742 aDesiredSize.BSize(wm) = 1743 CalcDesiredBSize(aReflowInput, borderPadding, aStatus); 1744 haveCalledCalcDesiredBSize = true; 1745 1746 mutable_rs.mFlags.mSpecialBSizeReflow = true; 1747 1748 ReflowTable(aDesiredSize, aReflowInput, borderPadding, 1749 TableReflowMode::Final, lastChildReflowed, aStatus); 1750 1751 mutable_rs.mFlags.mSpecialBSizeReflow = false; 1752 } 1753 } 1754 1755 if (aStatus.IsIncomplete() && 1756 aReflowInput.mStyleBorder->mBoxDecorationBreak == 1757 StyleBoxDecorationBreak::Slice) { 1758 borderPadding.BEnd(wm) = 0; 1759 } 1760 1761 aDesiredSize.ISize(wm) = 1762 aReflowInput.ComputedISize() + borderPadding.IStartEnd(wm); 1763 if (!haveCalledCalcDesiredBSize) { 1764 aDesiredSize.BSize(wm) = 1765 CalcDesiredBSize(aReflowInput, borderPadding, aStatus); 1766 } else if (lastChildReflowed && aStatus.IsIncomplete()) { 1767 // If there is an incomplete child, then set the desired block-size to 1768 // include it but not the next one. 1769 aDesiredSize.BSize(wm) = 1770 borderPadding.BEnd(wm) + 1771 lastChildReflowed->GetLogicalNormalRect(wm, containerSize).BEnd(wm); 1772 } 1773 if (IsRowInserted()) { 1774 ProcessRowInserted(aDesiredSize.BSize(wm)); 1775 } 1776 1777 // For more information on the reason for what we should do this, refer to the 1778 // code which defines and evaluates the variables xAdjustmentForAllKids and 1779 // tentativeContainerWidth in the previous part in this function. 1780 if (mayAdjustXForAllChildren) { 1781 nscoord xAdjustmentForAllKids = 1782 aDesiredSize.Width() - tentativeContainerWidth; 1783 if (0 != xAdjustmentForAllKids) { 1784 for (nsIFrame* kid : mFrames) { 1785 kid->MovePositionBy(nsPoint(xAdjustmentForAllKids, 0)); 1786 } 1787 } 1788 } 1789 1790 // Calculate the overflow area contribution from our children. We couldn't 1791 // do this on the fly during ReflowChildren(), because in vertical-rl mode 1792 // with unconstrained width, we weren't placing them in their final positions 1793 // until the fixupKidPositions loop just above. 1794 for (nsIFrame* kid : mFrames) { 1795 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid); 1796 } 1797 1798 SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding, 1799 aDesiredSize.PhysicalSize()); 1800 NS_WARNING_ASSERTION(NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(), 1801 "reflow branch removed unconstrained available isizes"); 1802 if (NeedToCollapse()) { 1803 // This code and the code it depends on assumes that all row groups 1804 // and rows have just been reflowed (i.e., it makes adjustments to 1805 // their rects that are not idempotent). Thus the reflow code 1806 // checks NeedToCollapse() to ensure this is true. 1807 AdjustForCollapsingRowsCols(aDesiredSize, wm, borderPadding); 1808 } 1809 1810 // If there are any relatively-positioned table parts, we need to reflow their 1811 // absolutely-positioned descendants now that their dimensions are final. 1812 FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowInput); 1813 1814 // make sure the table overflow area does include the table rect. 1815 nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()); 1816 1817 aDesiredSize.mOverflowAreas.UnionAllWith(tableRect); 1818 1819 FinishAndStoreOverflow(&aDesiredSize); 1820 } 1821 1822 void nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext, 1823 ReflowOutput& aDesiredSize, 1824 const ReflowInput& aReflowInput) { 1825 TablePartsArray* positionedParts = 1826 GetProperty(PositionedTablePartsProperty()); 1827 if (!positionedParts) { 1828 return; 1829 } 1830 1831 OverflowChangedTracker overflowTracker; 1832 overflowTracker.SetSubtreeRoot(this); 1833 1834 for (nsContainerFrame* positionedPart : *positionedParts) { 1835 // As we've already finished reflow, positionedParts's size and overflow 1836 // areas have already been assigned, so we just pull them back out. 1837 const WritingMode wm = positionedPart->GetWritingMode(); 1838 const LogicalSize size = positionedPart->GetLogicalSize(wm); 1839 ReflowOutput desiredSize(aReflowInput.GetWritingMode()); 1840 desiredSize.SetSize(wm, size); 1841 desiredSize.mOverflowAreas = 1842 positionedPart->GetOverflowAreasRelativeToSelf(); 1843 1844 // Construct a dummy reflow input and reflow status. 1845 // XXX(seth): Note that the dummy reflow input doesn't have a correct 1846 // chain of parent reflow inputs. It also doesn't necessarily have a 1847 // correct containing block. 1848 LogicalSize availSize = size; 1849 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 1850 ReflowInput reflowInput(aPresContext, positionedPart, 1851 aReflowInput.mRenderingContext, availSize, 1852 ReflowInput::InitFlag::DummyParentReflowInput); 1853 nsReflowStatus reflowStatus; 1854 1855 // Reflow absolutely-positioned descendants of the positioned part. 1856 // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the bsize and 1857 // ignoring any change to the reflow status aren't correct. We'll never 1858 // paginate absolutely positioned frames. 1859 positionedPart->FinishReflowWithAbsoluteFrames(PresContext(), desiredSize, 1860 reflowInput, reflowStatus); 1861 1862 // FinishReflowWithAbsoluteFrames has updated overflow on 1863 // |positionedPart|. We need to make sure that update propagates 1864 // through the intermediate frames between it and this frame. 1865 nsIFrame* positionedFrameParent = positionedPart->GetParent(); 1866 if (positionedFrameParent != this) { 1867 overflowTracker.AddFrame(positionedFrameParent, 1868 OverflowChangedTracker::CHILDREN_CHANGED); 1869 } 1870 } 1871 1872 // Propagate updated overflow areas up the tree. 1873 overflowTracker.Flush(); 1874 1875 // Update our own overflow areas. (OverflowChangedTracker doesn't update the 1876 // subtree root itself.) 1877 aDesiredSize.SetOverflowAreasToDesiredBounds(); 1878 nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas); 1879 } 1880 1881 bool nsTableFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { 1882 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); 1883 } 1884 1885 void nsTableFrame::ReflowTable(ReflowOutput& aDesiredSize, 1886 const ReflowInput& aReflowInput, 1887 const LogicalMargin& aBorderPadding, 1888 TableReflowMode aReflowMode, 1889 nsIFrame*& aLastChildReflowed, 1890 nsReflowStatus& aStatus) { 1891 aLastChildReflowed = nullptr; 1892 1893 if (!GetPrevInFlow()) { 1894 mTableLayoutStrategy->ComputeColumnISizes(aReflowInput); 1895 } 1896 1897 TableReflowInput reflowInput(aReflowInput, aBorderPadding, aReflowMode); 1898 ReflowChildren(reflowInput, aStatus, aLastChildReflowed, 1899 aDesiredSize.mOverflowAreas); 1900 1901 ReflowColGroups(aReflowInput.mRenderingContext); 1902 } 1903 1904 void nsTableFrame::PushChildrenToOverflow(const RowGroupArray& aRowGroups, 1905 size_t aPushFrom) { 1906 MOZ_ASSERT(aPushFrom > 0, "pushing first child"); 1907 1908 // Extract the frames from the array into a frame list. 1909 nsFrameList frames; 1910 for (size_t childX = aPushFrom; childX < aRowGroups.Length(); ++childX) { 1911 nsTableRowGroupFrame* rgFrame = aRowGroups[childX]; 1912 if (!rgFrame->IsRepeatable()) { 1913 mFrames.RemoveFrame(rgFrame); 1914 frames.AppendFrame(nullptr, rgFrame); 1915 } 1916 } 1917 1918 if (frames.IsEmpty()) { 1919 return; 1920 } 1921 1922 // Add the frames to our overflow list. 1923 SetOverflowFrames(std::move(frames)); 1924 } 1925 1926 // collapsing row groups, rows, col groups and cols are accounted for after both 1927 // passes of reflow so that it has no effect on the calculations of reflow. 1928 void nsTableFrame::AdjustForCollapsingRowsCols( 1929 ReflowOutput& aDesiredSize, const WritingMode aWM, 1930 const LogicalMargin& aBorderPadding) { 1931 nscoord bTotalOffset = 0; // total offset among all rows in all row groups 1932 1933 // reset the bit, it will be set again if row/rowgroup or col/colgroup are 1934 // collapsed 1935 SetNeedToCollapse(false); 1936 1937 // collapse the rows and/or row groups as necessary 1938 // Get the ordered children 1939 RowGroupArray rowGroups = OrderedRowGroups(); 1940 1941 nsTableFrame* firstInFlow = static_cast<nsTableFrame*>(FirstInFlow()); 1942 nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding); 1943 nscoord rgISize = iSize - GetColSpacing(-1) - GetColSpacing(GetColCount()); 1944 OverflowAreas overflow; 1945 // Walk the list of children 1946 for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) { 1947 nsTableRowGroupFrame* rgFrame = rowGroups[childX]; 1948 NS_ASSERTION(rgFrame, "Must have row group frame here"); 1949 bTotalOffset += 1950 rgFrame->CollapseRowGroupIfNecessary(bTotalOffset, rgISize, aWM); 1951 ConsiderChildOverflow(overflow, rgFrame); 1952 } 1953 1954 aDesiredSize.BSize(aWM) -= bTotalOffset; 1955 aDesiredSize.ISize(aWM) = iSize; 1956 overflow.UnionAllWith( 1957 nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height())); 1958 FinishAndStoreOverflow(overflow, 1959 nsSize(aDesiredSize.Width(), aDesiredSize.Height())); 1960 } 1961 1962 nscoord nsTableFrame::GetCollapsedISize(const WritingMode aWM, 1963 const LogicalMargin& aBorderPadding) { 1964 NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedISize called on next in flow"); 1965 nscoord iSize = GetColSpacing(GetColCount()); 1966 iSize += aBorderPadding.IStartEnd(aWM); 1967 nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow()); 1968 for (nsIFrame* groupFrame : mColGroups) { 1969 const nsStyleVisibility* groupVis = groupFrame->StyleVisibility(); 1970 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; 1971 nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame; 1972 for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame; 1973 colFrame = colFrame->GetNextCol()) { 1974 const nsStyleDisplay* colDisplay = colFrame->StyleDisplay(); 1975 nscoord colIdx = colFrame->GetColIndex(); 1976 if (mozilla::StyleDisplay::TableColumn == colDisplay->mDisplay) { 1977 const nsStyleVisibility* colVis = colFrame->StyleVisibility(); 1978 bool collapseCol = StyleVisibility::Collapse == colVis->mVisible; 1979 nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx); 1980 if (!collapseGroup && !collapseCol) { 1981 iSize += colISize; 1982 if (ColumnHasCellSpacingBefore(colIdx)) { 1983 iSize += GetColSpacing(colIdx - 1); 1984 } 1985 } else { 1986 SetNeedToCollapse(true); 1987 } 1988 } 1989 } 1990 } 1991 return iSize; 1992 } 1993 1994 /* virtual */ 1995 void nsTableFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { 1996 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); 1997 1998 if (!aOldComputedStyle) { // avoid this on init 1999 return; 2000 } 2001 2002 if (IsBorderCollapse() && BCRecalcNeeded(aOldComputedStyle, Style())) { 2003 SetFullBCDamageArea(); 2004 } 2005 2006 // avoid this on init or nextinflow 2007 if (!mTableLayoutStrategy || GetPrevInFlow()) { 2008 return; 2009 } 2010 2011 bool isAuto = IsAutoLayout(); 2012 if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) { 2013 if (isAuto) { 2014 mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this); 2015 } else { 2016 mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this); 2017 } 2018 } 2019 } 2020 2021 void nsTableFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { 2022 NS_ASSERTION(aListID == FrameChildListID::Principal || 2023 aListID == FrameChildListID::ColGroup, 2024 "unexpected child list"); 2025 2026 // Because we actually have two child lists, one for col group frames and one 2027 // for everything else, we need to look at each frame individually 2028 // XXX The frame construction code should be separating out child frames 2029 // based on the type, bug 343048. 2030 while (!aFrameList.IsEmpty()) { 2031 nsIFrame* f = aFrameList.FirstChild(); 2032 aFrameList.RemoveFrame(f); 2033 2034 // See what kind of frame we have 2035 const nsStyleDisplay* display = f->StyleDisplay(); 2036 2037 if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) { 2038 if (MOZ_UNLIKELY(GetPrevInFlow())) { 2039 nsFrameList colgroupFrame(f, f); 2040 auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow()); 2041 firstInFlow->AppendFrames(aListID, std::move(colgroupFrame)); 2042 continue; 2043 } 2044 nsTableColGroupFrame* lastColGroup = 2045 nsTableColGroupFrame::GetLastRealColGroup(this); 2046 int32_t startColIndex = (lastColGroup) 2047 ? lastColGroup->GetStartColumnIndex() + 2048 lastColGroup->GetColCount() 2049 : 0; 2050 mColGroups.InsertFrame(this, lastColGroup, f); 2051 // Insert the colgroup and its cols into the table 2052 InsertColGroups(startColIndex, 2053 nsFrameList::Slice(f, f->GetNextSibling())); 2054 } else if (IsRowGroup(display->mDisplay)) { 2055 DrainSelfOverflowList(); // ensure the last frame is in mFrames 2056 // Append the new row group frame to the sibling chain 2057 mFrames.AppendFrame(nullptr, f); 2058 2059 // insert the row group and its rows into the table 2060 InsertRowGroups(nsFrameList::Slice(f, nullptr)); 2061 } else { 2062 // Nothing special to do, just add the frame to our child list 2063 MOZ_ASSERT_UNREACHABLE( 2064 "How did we get here? Frame construction screwed up"); 2065 mFrames.AppendFrame(nullptr, f); 2066 } 2067 } 2068 2069 #ifdef DEBUG_TABLE_CELLMAP 2070 printf("=== TableFrame::AppendFrames\n"); 2071 Dump(true, true, true); 2072 #endif 2073 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 2074 NS_FRAME_HAS_DIRTY_CHILDREN); 2075 SetGeometryDirty(); 2076 } 2077 2078 void nsTableFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 2079 const nsLineList::iterator* aPrevFrameLine, 2080 nsFrameList&& aFrameList) { 2081 // The frames in aFrameList can be a mix of row group frames and col group 2082 // frames. The problem is that they should go in separate child lists so 2083 // we need to deal with that here... 2084 // XXX The frame construction code should be separating out child frames 2085 // based on the type, bug 343048. 2086 2087 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 2088 "inserting after sibling frame with different parent"); 2089 2090 if ((aPrevFrame && !aPrevFrame->GetNextSibling()) || 2091 (!aPrevFrame && GetChildList(aListID).IsEmpty())) { 2092 // Treat this like an append; still a workaround for bug 343048. 2093 AppendFrames(aListID, std::move(aFrameList)); 2094 return; 2095 } 2096 2097 // Collect ColGroupFrames into a separate list and insert those separately 2098 // from the other frames (bug 759249). 2099 nsFrameList colGroupList; 2100 nsFrameList principalList; 2101 do { 2102 const auto display = aFrameList.FirstChild()->StyleDisplay()->mDisplay; 2103 nsFrameList head = aFrameList.Split([display](nsIFrame* aFrame) { 2104 return aFrame->StyleDisplay()->mDisplay != display; 2105 }); 2106 if (display == mozilla::StyleDisplay::TableColumnGroup) { 2107 colGroupList.AppendFrames(nullptr, std::move(head)); 2108 } else { 2109 principalList.AppendFrames(nullptr, std::move(head)); 2110 } 2111 } while (aFrameList.NotEmpty()); 2112 2113 // We pass aPrevFrame for both ColGroup and other frames since 2114 // HomogenousInsertFrames will only use it if it's a suitable 2115 // prev-sibling for the frames in the frame list. 2116 if (colGroupList.NotEmpty()) { 2117 HomogenousInsertFrames(FrameChildListID::ColGroup, aPrevFrame, 2118 colGroupList); 2119 } 2120 if (principalList.NotEmpty()) { 2121 HomogenousInsertFrames(FrameChildListID::Principal, aPrevFrame, 2122 principalList); 2123 } 2124 } 2125 2126 void nsTableFrame::HomogenousInsertFrames(ChildListID aListID, 2127 nsIFrame* aPrevFrame, 2128 nsFrameList& aFrameList) { 2129 // See what kind of frame we have 2130 const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay(); 2131 bool isColGroup = 2132 mozilla::StyleDisplay::TableColumnGroup == display->mDisplay; 2133 #ifdef DEBUG 2134 // Verify that either all siblings have display:table-column-group, or they 2135 // all have display values different from table-column-group. 2136 for (nsIFrame* frame : aFrameList) { 2137 auto nextDisplay = frame->StyleDisplay()->mDisplay; 2138 MOZ_ASSERT( 2139 isColGroup == (nextDisplay == mozilla::StyleDisplay::TableColumnGroup), 2140 "heterogenous childlist"); 2141 } 2142 #endif 2143 if (MOZ_UNLIKELY(isColGroup && GetPrevInFlow())) { 2144 auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow()); 2145 firstInFlow->AppendFrames(aListID, std::move(aFrameList)); 2146 return; 2147 } 2148 if (aPrevFrame) { 2149 const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay(); 2150 // Make sure they belong on the same frame list 2151 if ((display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) != 2152 (prevDisplay->mDisplay == mozilla::StyleDisplay::TableColumnGroup)) { 2153 // the previous frame is not valid, see comment at ::AppendFrames 2154 // XXXbz Using content indices here means XBL will get screwed 2155 // over... Oh, well. 2156 nsIFrame* pseudoFrame = aFrameList.FirstChild(); 2157 nsIContent* parentContent = GetContent(); 2158 nsIContent* content = nullptr; 2159 aPrevFrame = nullptr; 2160 while (pseudoFrame && 2161 (parentContent == (content = pseudoFrame->GetContent()))) { 2162 pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild(); 2163 } 2164 nsCOMPtr<nsIContent> container = content->GetParent(); 2165 if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823. 2166 const Maybe<uint32_t> newIndex = container->ComputeIndexOf(content); 2167 nsIFrame* kidFrame; 2168 nsTableColGroupFrame* lastColGroup = nullptr; 2169 if (isColGroup) { 2170 kidFrame = mColGroups.FirstChild(); 2171 lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this); 2172 } else { 2173 kidFrame = mFrames.FirstChild(); 2174 } 2175 // Important: need to start at a value smaller than all valid indices 2176 Maybe<uint32_t> lastIndex; 2177 while (kidFrame) { 2178 if (isColGroup) { 2179 if (kidFrame == lastColGroup) { 2180 aPrevFrame = 2181 kidFrame; // there is no real colgroup after this one 2182 break; 2183 } 2184 } 2185 pseudoFrame = kidFrame; 2186 while (pseudoFrame && 2187 (parentContent == (content = pseudoFrame->GetContent()))) { 2188 pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild(); 2189 } 2190 const Maybe<uint32_t> index = container->ComputeIndexOf(content); 2191 // XXX Keep the odd traditional behavior in some indices are nothing 2192 // cases for now. 2193 if ((index.isSome() && 2194 (lastIndex.isNothing() || *index > *lastIndex)) && 2195 (newIndex.isSome() && 2196 (index.isNothing() || *index < *newIndex))) { 2197 lastIndex = index; 2198 aPrevFrame = kidFrame; 2199 } 2200 kidFrame = kidFrame->GetNextSibling(); 2201 } 2202 } 2203 } 2204 } 2205 if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) { 2206 NS_ASSERTION(aListID == FrameChildListID::ColGroup, 2207 "unexpected child list"); 2208 // Insert the column group frames 2209 const nsFrameList::Slice& newColgroups = 2210 mColGroups.InsertFrames(this, aPrevFrame, std::move(aFrameList)); 2211 // find the starting col index for the first new col group 2212 int32_t startColIndex = 0; 2213 if (aPrevFrame) { 2214 nsTableColGroupFrame* prevColGroup = 2215 (nsTableColGroupFrame*)GetFrameAtOrBefore( 2216 this, aPrevFrame, LayoutFrameType::TableColGroup); 2217 if (prevColGroup) { 2218 startColIndex = 2219 prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount(); 2220 } 2221 } 2222 InsertColGroups(startColIndex, newColgroups); 2223 } else if (IsRowGroup(display->mDisplay)) { 2224 NS_ASSERTION(aListID == FrameChildListID::Principal, 2225 "unexpected child list"); 2226 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames 2227 // Insert the frames in the sibling chain 2228 const nsFrameList::Slice& newRowGroups = 2229 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); 2230 2231 InsertRowGroups(newRowGroups); 2232 } else { 2233 NS_ASSERTION(aListID == FrameChildListID::Principal, 2234 "unexpected child list"); 2235 MOZ_ASSERT_UNREACHABLE("How did we even get here?"); 2236 // Just insert the frame and don't worry about reflowing it 2237 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); 2238 return; 2239 } 2240 2241 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, 2242 NS_FRAME_HAS_DIRTY_CHILDREN); 2243 SetGeometryDirty(); 2244 #ifdef DEBUG_TABLE_CELLMAP 2245 printf("=== TableFrame::InsertFrames\n"); 2246 Dump(true, true, true); 2247 #endif 2248 } 2249 2250 void nsTableFrame::DoRemoveFrame(DestroyContext& aContext, ChildListID aListID, 2251 nsIFrame* aOldFrame) { 2252 if (aListID == FrameChildListID::ColGroup) { 2253 nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling(); 2254 nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame; 2255 int32_t firstColIndex = colGroup->GetStartColumnIndex(); 2256 int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1; 2257 mColGroups.DestroyFrame(aContext, aOldFrame); 2258 nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex); 2259 // remove the cols from the table 2260 int32_t colIdx; 2261 for (colIdx = lastColIndex; colIdx >= firstColIndex; colIdx--) { 2262 nsTableColFrame* colFrame = mColFrames.SafeElementAt(colIdx); 2263 if (colFrame) { 2264 RemoveCol(colGroup, colIdx, true, false); 2265 } 2266 } 2267 2268 // If we have some anonymous cols at the end already, we just 2269 // add more of them. 2270 if (!mColFrames.IsEmpty() && 2271 mColFrames.LastElement() && // XXXbz is this ever null? 2272 mColFrames.LastElement()->GetColType() == eColAnonymousCell) { 2273 int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length(); 2274 if (numAnonymousColsToAdd > 0) { 2275 // this sets the child list, updates the col cache and cell map 2276 AppendAnonymousColFrames(numAnonymousColsToAdd); 2277 } 2278 } else { 2279 // All of our colframes correspond to actual <col> tags. It's possible 2280 // that we still have at least as many <col> tags as we have logical 2281 // columns from cells, but we might have one less. Handle the latter case 2282 // as follows: First ask the cellmap to drop its last col if it doesn't 2283 // have any actual cells in it. Then call MatchCellMapToColCache to 2284 // append an anonymous column if it's needed; this needs to be after 2285 // RemoveColsAtEnd, since it will determine the need for a new column 2286 // frame based on the width of the cell map. 2287 nsTableCellMap* cellMap = GetCellMap(); 2288 if (cellMap) { // XXXbz is this ever null? 2289 cellMap->RemoveColsAtEnd(); 2290 MatchCellMapToColCache(cellMap); 2291 } 2292 } 2293 2294 } else { 2295 NS_ASSERTION(aListID == FrameChildListID::Principal, 2296 "unexpected child list"); 2297 nsTableRowGroupFrame* rgFrame = 2298 static_cast<nsTableRowGroupFrame*>(aOldFrame); 2299 // remove the row group from the cell map 2300 nsTableCellMap* cellMap = GetCellMap(); 2301 if (cellMap) { 2302 cellMap->RemoveGroupCellMap(rgFrame); 2303 } 2304 2305 // remove the row group frame from the sibling chain 2306 mFrames.DestroyFrame(aContext, aOldFrame); 2307 2308 // the removal of a row group changes the cellmap, the columns might change 2309 if (cellMap) { 2310 cellMap->Synchronize(this); 2311 // Create an empty slice 2312 ResetRowIndices(nsFrameList::Slice(nullptr, nullptr)); 2313 TableArea damageArea; 2314 cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, 2315 damageArea); 2316 2317 static_cast<nsTableFrame*>(FirstInFlow()) 2318 ->MatchCellMapToColCache(cellMap); 2319 } 2320 } 2321 } 2322 2323 void nsTableFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, 2324 nsIFrame* aOldFrame) { 2325 NS_ASSERTION(aListID == FrameChildListID::ColGroup || 2326 mozilla::StyleDisplay::TableColumnGroup != 2327 aOldFrame->StyleDisplay()->mDisplay, 2328 "Wrong list name; use FrameChildListID::ColGroup iff colgroup"); 2329 mozilla::PresShell* presShell = PresShell(); 2330 nsTableFrame* lastParent = nullptr; 2331 while (aOldFrame) { 2332 nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation(); 2333 nsTableFrame* parent = static_cast<nsTableFrame*>(aOldFrame->GetParent()); 2334 if (parent != lastParent) { 2335 parent->DrainSelfOverflowList(); 2336 } 2337 parent->DoRemoveFrame(aContext, aListID, aOldFrame); 2338 aOldFrame = oldFrameNextContinuation; 2339 if (parent != lastParent) { 2340 // for now, just bail and recalc all of the collapsing borders 2341 // as the cellmap changes we need to recalc 2342 if (parent->IsBorderCollapse()) { 2343 parent->SetFullBCDamageArea(); 2344 } 2345 parent->SetGeometryDirty(); 2346 presShell->FrameNeedsReflow(parent, IntrinsicDirty::FrameAndAncestors, 2347 NS_FRAME_HAS_DIRTY_CHILDREN); 2348 lastParent = parent; 2349 } 2350 } 2351 #ifdef DEBUG_TABLE_CELLMAP 2352 printf("=== TableFrame::RemoveFrame\n"); 2353 Dump(true, true, true); 2354 #endif 2355 } 2356 2357 /* virtual */ 2358 nsMargin nsTableFrame::GetUsedBorder() const { 2359 if (!IsBorderCollapse()) { 2360 return nsContainerFrame::GetUsedBorder(); 2361 } 2362 2363 WritingMode wm = GetWritingMode(); 2364 return GetOuterBCBorder(wm).GetPhysicalMargin(wm); 2365 } 2366 2367 /* virtual */ 2368 nsMargin nsTableFrame::GetUsedPadding() const { 2369 if (!IsBorderCollapse()) { 2370 return nsContainerFrame::GetUsedPadding(); 2371 } 2372 2373 return nsMargin(0, 0, 0, 0); 2374 } 2375 2376 /* virtual */ 2377 nsMargin nsTableFrame::GetUsedMargin() const { 2378 // The margin is inherited to the table wrapper frame via 2379 // the ::-moz-table-wrapper rule in ua.css. 2380 return nsMargin(0, 0, 0, 0); 2381 } 2382 2383 // TODO(TYLin, dshin): This ideally should be set only in first-in-flow. 2384 // However, the current implementation of border-collapsed table does not 2385 // handle continuation gracefully. One concrete issue is shown in bug 1881157 2386 // comment 3. It is also unclear if the damage area, current included in this 2387 // property, should be stored separately per-continuation. 2388 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCDataProperty, TableBCData) 2389 2390 TableBCData* nsTableFrame::GetTableBCData() const { 2391 return GetProperty(TableBCDataProperty()); 2392 } 2393 2394 TableBCData* nsTableFrame::GetOrCreateTableBCData() { 2395 TableBCData* value = GetProperty(TableBCDataProperty()); 2396 if (!value) { 2397 value = new TableBCData(); 2398 SetProperty(TableBCDataProperty(), value); 2399 } 2400 2401 MOZ_ASSERT(value, "TableBCData must exist!"); 2402 return value; 2403 } 2404 2405 static void DivideBCBorderSize(nscoord aPixelSize, nscoord& aSmallHalf, 2406 nscoord& aLargeHalf) { 2407 aSmallHalf = aPixelSize / 2; 2408 aLargeHalf = aPixelSize - aSmallHalf; 2409 } 2410 2411 LogicalMargin nsTableFrame::GetOuterBCBorder(const WritingMode aWM) const { 2412 if (NeedToCalcBCBorders()) { 2413 const_cast<nsTableFrame*>(this)->CalcBCBorders(); 2414 } 2415 TableBCData* propData = GetTableBCData(); 2416 if (propData) { 2417 return LogicalMargin(aWM, 2418 BC_BORDER_START_HALF(propData->mBStartBorderWidth), 2419 BC_BORDER_END_HALF(propData->mIEndBorderWidth), 2420 BC_BORDER_END_HALF(propData->mBEndBorderWidth), 2421 BC_BORDER_START_HALF(propData->mIStartBorderWidth)); 2422 } 2423 return LogicalMargin(aWM); 2424 } 2425 2426 void nsTableFrame::GetCollapsedBorderPadding( 2427 Maybe<LogicalMargin>& aBorder, Maybe<LogicalMargin>& aPadding) const { 2428 if (IsBorderCollapse()) { 2429 // Border-collapsed tables don't use any of their padding, and only part of 2430 // their border. 2431 const auto wm = GetWritingMode(); 2432 aBorder.emplace(GetOuterBCBorder(wm)); 2433 aPadding.emplace(wm); 2434 } 2435 } 2436 2437 void nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput) { 2438 const auto childWM = aReflowInput.GetWritingMode(); 2439 LogicalMargin border(childWM); 2440 if (IsBorderCollapse()) { 2441 nsTableRowGroupFrame* rgFrame = 2442 static_cast<nsTableRowGroupFrame*>(aReflowInput.mFrame); 2443 border = rgFrame->GetBCBorderWidth(childWM); 2444 } 2445 const LogicalMargin zeroPadding(childWM); 2446 aReflowInput.Init(PresContext(), Nothing(), Some(border), Some(zeroPadding)); 2447 2448 NS_ASSERTION(!mBits.mResizedColumns || 2449 !aReflowInput.mParentReflowInput->mFlags.mSpecialBSizeReflow, 2450 "should not resize columns on special bsize reflow"); 2451 if (mBits.mResizedColumns) { 2452 aReflowInput.SetIResize(true); 2453 } 2454 } 2455 2456 // Position and size aKidFrame and update our reflow input. The origin of 2457 // aKidRect is relative to the upper-left origin of our frame 2458 void nsTableFrame::PlaceChild(TableReflowInput& aReflowInput, 2459 nsIFrame* aKidFrame, 2460 const ReflowInput& aKidReflowInput, 2461 const mozilla::LogicalPoint& aKidPosition, 2462 const nsSize& aContainerSize, 2463 ReflowOutput& aKidDesiredSize, 2464 const nsRect& aOriginalKidRect, 2465 const nsRect& aOriginalKidInkOverflow) { 2466 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode(); 2467 bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 2468 2469 // Place and size the child 2470 FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, &aKidReflowInput, 2471 wm, aKidPosition, aContainerSize, 2472 ReflowChildFlags::ApplyRelativePositioning); 2473 2474 InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidInkOverflow, 2475 isFirstReflow); 2476 2477 aReflowInput.AdvanceBCoord(aKidDesiredSize.BSize(wm)); 2478 } 2479 2480 nsTableFrame::RowGroupArray nsTableFrame::OrderedRowGroups( 2481 nsTableRowGroupFrame** aHead, nsTableRowGroupFrame** aFoot) const { 2482 RowGroupArray children; 2483 nsTableRowGroupFrame* head = nullptr; 2484 nsTableRowGroupFrame* foot = nullptr; 2485 2486 nsIFrame* kidFrame = mFrames.FirstChild(); 2487 while (kidFrame) { 2488 const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay(); 2489 auto* rowGroup = static_cast<nsTableRowGroupFrame*>(kidFrame); 2490 2491 switch (kidDisplay->DisplayInside()) { 2492 case StyleDisplayInside::TableHeaderGroup: 2493 if (head) { // treat additional thead like tbody 2494 children.AppendElement(rowGroup); 2495 } else { 2496 head = rowGroup; 2497 } 2498 break; 2499 case StyleDisplayInside::TableFooterGroup: 2500 if (foot) { // treat additional tfoot like tbody 2501 children.AppendElement(rowGroup); 2502 } else { 2503 foot = rowGroup; 2504 } 2505 break; 2506 case StyleDisplayInside::TableRowGroup: 2507 children.AppendElement(rowGroup); 2508 break; 2509 default: 2510 MOZ_ASSERT_UNREACHABLE("How did this produce an nsTableRowGroupFrame?"); 2511 // Just ignore it 2512 break; 2513 } 2514 // Get the next sibling but skip it if it's also the next-in-flow, since 2515 // a next-in-flow will not be part of the current table. 2516 while (kidFrame) { 2517 nsIFrame* nif = kidFrame->GetNextInFlow(); 2518 kidFrame = kidFrame->GetNextSibling(); 2519 if (kidFrame != nif) { 2520 break; 2521 } 2522 } 2523 } 2524 2525 // put the thead first 2526 if (head) { 2527 children.InsertElementAt(0, head); 2528 } 2529 if (aHead) { 2530 *aHead = head; 2531 } 2532 // put the tfoot after the last tbody 2533 if (foot) { 2534 children.AppendElement(foot); 2535 } 2536 if (aFoot) { 2537 *aFoot = foot; 2538 } 2539 2540 return children; 2541 } 2542 2543 static bool IsRepeatable(nscoord aFrameBSize, nscoord aPageBSize) { 2544 return aFrameBSize < (aPageBSize / 4); 2545 } 2546 2547 nscoord nsTableFrame::SetupHeaderFooterChild( 2548 const TableReflowInput& aReflowInput, nsTableRowGroupFrame* aFrame) { 2549 nsPresContext* presContext = PresContext(); 2550 const WritingMode wm = GetWritingMode(); 2551 const nscoord pageBSize = 2552 LogicalSize(wm, presContext->GetPageSize()).BSize(wm); 2553 2554 // Reflow the child with unconstrained block-size. 2555 LogicalSize availSize = aReflowInput.AvailableSize(); 2556 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 2557 2558 const nsSize containerSize = 2559 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); 2560 ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput, aFrame, 2561 availSize, Nothing(), 2562 ReflowInput::InitFlag::CallerWillInit); 2563 InitChildReflowInput(kidReflowInput); 2564 kidReflowInput.mFlags.mIsTopOfPage = true; 2565 ReflowOutput desiredSize(aReflowInput.mReflowInput); 2566 nsReflowStatus status; 2567 ReflowChild(aFrame, presContext, desiredSize, kidReflowInput, wm, 2568 LogicalPoint(wm, aReflowInput.mICoord, aReflowInput.mBCoord), 2569 containerSize, ReflowChildFlags::Default, status); 2570 // The child will be reflowed again "for real" so no need to place it now 2571 2572 aFrame->SetRepeatable(IsRepeatable(desiredSize.BSize(wm), pageBSize)); 2573 return desiredSize.BSize(wm); 2574 } 2575 2576 void nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput, 2577 nsTableRowGroupFrame* aTfoot, 2578 nscoord aFooterBSize) { 2579 nsPresContext* presContext = PresContext(); 2580 const WritingMode wm = GetWritingMode(); 2581 LogicalSize kidAvailSize = aReflowInput.AvailableSize(); 2582 kidAvailSize.BSize(wm) = aFooterBSize; 2583 2584 const nsSize containerSize = 2585 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); 2586 ReflowInput footerReflowInput(presContext, aReflowInput.mReflowInput, aTfoot, 2587 kidAvailSize, Nothing(), 2588 ReflowInput::InitFlag::CallerWillInit); 2589 InitChildReflowInput(footerReflowInput); 2590 2591 nsRect origTfootRect = aTfoot->GetRect(); 2592 nsRect origTfootInkOverflow = aTfoot->InkOverflowRect(); 2593 2594 nsReflowStatus footerStatus; 2595 ReflowOutput desiredSize(aReflowInput.mReflowInput); 2596 LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord); 2597 ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput, wm, 2598 kidPosition, containerSize, ReflowChildFlags::Default, 2599 footerStatus); 2600 2601 PlaceChild(aReflowInput, aTfoot, footerReflowInput, kidPosition, 2602 containerSize, desiredSize, origTfootRect, origTfootInkOverflow); 2603 } 2604 2605 // Reflow the children based on the avail size and reason in aReflowInput 2606 void nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput, 2607 nsReflowStatus& aStatus, 2608 nsIFrame*& aLastChildReflowed, 2609 OverflowAreas& aOverflowAreas) { 2610 aStatus.Reset(); 2611 aLastChildReflowed = nullptr; 2612 2613 nsIFrame* prevKidFrame = nullptr; 2614 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode(); 2615 NS_WARNING_ASSERTION( 2616 wm.IsVertical() || 2617 NS_UNCONSTRAINEDSIZE != aReflowInput.mReflowInput.ComputedWidth(), 2618 "shouldn't have unconstrained width in horizontal mode"); 2619 nsSize containerSize = 2620 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); 2621 2622 nsPresContext* presContext = PresContext(); 2623 // nsTableFrame is not able to pull back children from its next-in-flow, per 2624 // bug 1772383. So even under paginated contexts, tables should not fragment 2625 // if they are inside of (i.e. potentially being fragmented by) a column-set 2626 // frame. (This is indicated by the "mTableIsSplittable" flag.) 2627 bool isPaginated = 2628 presContext->IsPaginated() && 2629 aReflowInput.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 2630 aReflowInput.mReflowInput.mFlags.mTableIsSplittable; 2631 2632 // Tables currently (though we ought to fix this) only fragment in 2633 // paginated contexts, not in multicolumn contexts. (See bug 888257.) 2634 // This is partly because they don't correctly handle incremental 2635 // layout when paginated. 2636 // 2637 // Since we propagate NS_FRAME_IS_DIRTY from parent to child at the 2638 // start of the parent's reflow (behavior that's new as of bug 2639 // 1308876), we can do things that are effectively incremental reflow 2640 // during paginated layout. Since the table code doesn't handle this 2641 // correctly, we need to set the flag that says to reflow everything 2642 // within the table structure. 2643 if (presContext->IsPaginated()) { 2644 SetGeometryDirty(); 2645 } 2646 2647 aOverflowAreas.Clear(); 2648 2649 bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() || 2650 mBits.mResizedColumns || IsGeometryDirty() || 2651 NeedToCollapse(); 2652 2653 nsTableRowGroupFrame* thead = nullptr; 2654 nsTableRowGroupFrame* tfoot = nullptr; 2655 RowGroupArray rowGroups = OrderedRowGroups(&thead, &tfoot); 2656 bool pageBreak = false; 2657 nscoord footerBSize = 0; 2658 2659 // Determine the repeatablility of headers and footers, and also the desired 2660 // height of any repeatable footer. 2661 // The repeatability of headers on continued tables is handled 2662 // when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame. 2663 // We handle the repeatability of footers again here because we need to 2664 // determine the footer's height anyway. We could perhaps optimize by 2665 // using the footer's prev-in-flow's height instead of reflowing it again, 2666 // but there's no real need. 2667 if (isPaginated) { 2668 bool reorder = false; 2669 if (thead && !GetPrevInFlow()) { 2670 reorder = thead->GetNextInFlow(); 2671 SetupHeaderFooterChild(aReflowInput, thead); 2672 } 2673 if (tfoot) { 2674 reorder = reorder || tfoot->GetNextInFlow(); 2675 footerBSize = SetupHeaderFooterChild(aReflowInput, tfoot); 2676 } 2677 if (reorder) { 2678 // Reorder row groups - the reflow may have changed the nextinflows. 2679 rowGroups = OrderedRowGroups(&thead, &tfoot); 2680 } 2681 } 2682 bool allowRepeatedFooter = false; 2683 for (size_t childX = 0; childX < rowGroups.Length(); childX++) { 2684 nsTableRowGroupFrame* kidFrame = rowGroups[childX]; 2685 const nscoord rowSpacing = 2686 GetRowSpacing(kidFrame->GetStartRowIndex() + kidFrame->GetRowCount()); 2687 // See if we should only reflow the dirty child frames 2688 if (reflowAllKids || kidFrame->IsSubtreeDirty() || 2689 (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow && 2690 (isPaginated || 2691 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { 2692 // A helper to place a repeated footer if allowed, or set it as 2693 // non-repeatable. 2694 auto MaybePlaceRepeatedFooter = [&]() { 2695 if (allowRepeatedFooter) { 2696 PlaceRepeatedFooter(aReflowInput, tfoot, footerBSize); 2697 } else if (tfoot && tfoot->IsRepeatable()) { 2698 tfoot->SetRepeatable(false); 2699 } 2700 }; 2701 2702 if (pageBreak) { 2703 MaybePlaceRepeatedFooter(); 2704 PushChildrenToOverflow(rowGroups, childX); 2705 aStatus.Reset(); 2706 aStatus.SetIncomplete(); 2707 aLastChildReflowed = allowRepeatedFooter ? tfoot : prevKidFrame; 2708 break; 2709 } 2710 2711 LogicalSize kidAvailSize = aReflowInput.AvailableSize(); 2712 allowRepeatedFooter = false; 2713 2714 // If the child is a tbody in paginated mode, reduce the available 2715 // block-size by a repeated footer. 2716 if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) { 2717 if (kidFrame != thead && kidFrame != tfoot && tfoot && 2718 tfoot->IsRepeatable()) { 2719 // the child is a tbody and there is a repeatable footer 2720 NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1], 2721 "Missing footer!"); 2722 if (footerBSize + rowSpacing < kidAvailSize.BSize(wm)) { 2723 allowRepeatedFooter = true; 2724 kidAvailSize.BSize(wm) -= footerBSize + rowSpacing; 2725 } 2726 } 2727 } 2728 2729 nsRect oldKidRect = kidFrame->GetRect(); 2730 nsRect oldKidInkOverflow = kidFrame->InkOverflowRect(); 2731 2732 ReflowOutput desiredSize(aReflowInput.mReflowInput); 2733 2734 // Reflow the child into the available space 2735 ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput, 2736 kidFrame, kidAvailSize, Nothing(), 2737 ReflowInput::InitFlag::CallerWillInit); 2738 InitChildReflowInput(kidReflowInput); 2739 2740 // If this isn't the first row group, and the previous row group has a 2741 // nonzero BEnd, then we can't be at the top of the page. 2742 // We ignore a repeated head row group in this check to avoid causing 2743 // infinite loops in some circumstances - see bug 344883. 2744 if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) && 2745 (rowGroups[childX - 1] 2746 ->GetLogicalNormalRect(wm, containerSize) 2747 .BEnd(wm) > 0)) { 2748 kidReflowInput.mFlags.mIsTopOfPage = false; 2749 } 2750 2751 // record the presence of a next in flow, it might get destroyed so we 2752 // need to reorder the row group array 2753 const bool reorder = kidFrame->GetNextInFlow(); 2754 2755 LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord); 2756 aStatus.Reset(); 2757 ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput, wm, 2758 kidPosition, containerSize, ReflowChildFlags::Default, 2759 aStatus); 2760 2761 if (reorder) { 2762 // Reorder row groups - the reflow may have changed the nextinflows. 2763 rowGroups = OrderedRowGroups(&thead, &tfoot); 2764 childX = rowGroups.IndexOf(kidFrame); 2765 MOZ_ASSERT(childX != RowGroupArray::NoIndex, 2766 "kidFrame should still be in rowGroups!"); 2767 } 2768 if (isPaginated && !aStatus.IsFullyComplete() && 2769 ShouldAvoidBreakInside(aReflowInput.mReflowInput)) { 2770 aStatus.SetInlineLineBreakBeforeAndReset(); 2771 break; 2772 } 2773 // see if the rowgroup did not fit on this page might be pushed on 2774 // the next page 2775 if (isPaginated && 2776 (aStatus.IsInlineBreakBefore() || 2777 (aStatus.IsComplete() && 2778 (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) && 2779 kidReflowInput.AvailableBSize() < desiredSize.BSize(wm)))) { 2780 if (ShouldAvoidBreakInside(aReflowInput.mReflowInput)) { 2781 aStatus.SetInlineLineBreakBeforeAndReset(); 2782 break; 2783 } 2784 // if we are on top of the page place with dataloss 2785 if (kidReflowInput.mFlags.mIsTopOfPage) { 2786 if (childX + 1 < rowGroups.Length()) { 2787 PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition, 2788 containerSize, desiredSize, oldKidRect, 2789 oldKidInkOverflow); 2790 MaybePlaceRepeatedFooter(); 2791 aStatus.Reset(); 2792 aStatus.SetIncomplete(); 2793 PushChildrenToOverflow(rowGroups, childX + 1); 2794 aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame; 2795 break; 2796 } 2797 } else { // we are not on top, push this rowgroup onto the next page 2798 if (prevKidFrame) { // we had a rowgroup before so push this 2799 MaybePlaceRepeatedFooter(); 2800 aStatus.Reset(); 2801 aStatus.SetIncomplete(); 2802 PushChildrenToOverflow(rowGroups, childX); 2803 aLastChildReflowed = allowRepeatedFooter ? tfoot : prevKidFrame; 2804 break; 2805 } else { // we can't push so lets make clear how much space we need 2806 PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition, 2807 containerSize, desiredSize, oldKidRect, 2808 oldKidInkOverflow); 2809 MaybePlaceRepeatedFooter(); 2810 aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame; 2811 break; 2812 } 2813 } 2814 } 2815 2816 aLastChildReflowed = kidFrame; 2817 2818 pageBreak = false; 2819 // see if there is a page break after this row group or before the next 2820 // one 2821 if (aStatus.IsComplete() && isPaginated && 2822 (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) { 2823 nsIFrame* nextKid = 2824 (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr; 2825 pageBreak = PageBreakAfter(kidFrame, nextKid); 2826 } 2827 2828 // Place the child 2829 PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition, 2830 containerSize, desiredSize, oldKidRect, oldKidInkOverflow); 2831 aReflowInput.AdvanceBCoord(rowSpacing); 2832 2833 // Remember where we just were in case we end up pushing children 2834 prevKidFrame = kidFrame; 2835 2836 MOZ_ASSERT(!aStatus.IsIncomplete() || isPaginated, 2837 "Table contents should only fragment in paginated contexts"); 2838 2839 // Special handling for incomplete children 2840 if (isPaginated && aStatus.IsIncomplete()) { 2841 nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow(); 2842 if (!kidNextInFlow) { 2843 // The child doesn't have a next-in-flow so create a continuing 2844 // frame. This hooks the child into the flow 2845 kidNextInFlow = 2846 PresShell()->FrameConstructor()->CreateContinuingFrame(kidFrame, 2847 this); 2848 2849 // Insert the kid's new next-in-flow into our sibling list... 2850 mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow); 2851 // and in rowGroups after childX so that it will get pushed below. 2852 rowGroups.InsertElementAt( 2853 childX + 1, static_cast<nsTableRowGroupFrame*>(kidNextInFlow)); 2854 } else if (kidNextInFlow == kidFrame->GetNextSibling()) { 2855 // OrderedRowGroups excludes NIFs in the child list from 'rowGroups' 2856 // so we deal with that here to make sure they get pushed. 2857 MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow), 2858 "OrderedRowGroups must not put our NIF in 'rowGroups'"); 2859 rowGroups.InsertElementAt( 2860 childX + 1, static_cast<nsTableRowGroupFrame*>(kidNextInFlow)); 2861 } 2862 2863 // We've used up all of our available space so push the remaining 2864 // children. 2865 MaybePlaceRepeatedFooter(); 2866 if (kidFrame->GetNextSibling()) { 2867 PushChildrenToOverflow(rowGroups, childX + 1); 2868 } 2869 aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame; 2870 break; 2871 } 2872 } else { // it isn't being reflowed 2873 aReflowInput.AdvanceBCoord(rowSpacing); 2874 const LogicalRect kidRect = 2875 kidFrame->GetLogicalNormalRect(wm, containerSize); 2876 if (kidRect.BStart(wm) != aReflowInput.mBCoord) { 2877 // invalidate the old position 2878 kidFrame->InvalidateFrameSubtree(); 2879 // move to the new position 2880 kidFrame->MovePositionBy( 2881 wm, LogicalPoint(wm, 0, aReflowInput.mBCoord - kidRect.BStart(wm))); 2882 // invalidate the new position 2883 kidFrame->InvalidateFrameSubtree(); 2884 } 2885 2886 aReflowInput.AdvanceBCoord(kidRect.BSize(wm)); 2887 } 2888 } 2889 2890 // We've now propagated the column resizes and geometry changes to all 2891 // the children. 2892 mBits.mResizedColumns = false; 2893 ClearGeometryDirty(); 2894 2895 // nsTableFrame does not pull children from its next-in-flow (bug 1772383). 2896 // This is generally fine, since tables only fragment for printing 2897 // (bug 888257) where incremental-reflow is impossible, and so children don't 2898 // usually dynamically move back and forth between continuations. However, 2899 // there are edge cases even with printing where nsTableFrame: 2900 // (1) Generates a continuation and passes children to it, 2901 // (2) Receives another call to Reflow, during which it 2902 // (3) Successfully lays out its remaining children. 2903 // If the completed status flows up as-is, the continuation will be destroyed. 2904 // To avoid that, we return an incomplete status if the continuation contains 2905 // any child that is not a repeated frame. 2906 auto hasNextInFlowThatMustBePreserved = [this, isPaginated]() -> bool { 2907 if (!isPaginated) { 2908 return false; 2909 } 2910 auto* nextInFlow = static_cast<nsTableFrame*>(GetNextInFlow()); 2911 if (!nextInFlow) { 2912 return false; 2913 } 2914 for (nsIFrame* kidFrame : nextInFlow->mFrames) { 2915 if (!IsRepeatedFrame(kidFrame)) { 2916 return true; 2917 } 2918 } 2919 return false; 2920 }; 2921 if (aStatus.IsComplete() && hasNextInFlowThatMustBePreserved()) { 2922 aStatus.SetIncomplete(); 2923 } 2924 } 2925 2926 void nsTableFrame::ReflowColGroups(gfxContext* aRenderingContext) { 2927 if (!GetPrevInFlow() && !HaveReflowedColGroups()) { 2928 const WritingMode wm = GetWritingMode(); 2929 nsPresContext* presContext = PresContext(); 2930 for (nsIFrame* kidFrame : mColGroups) { 2931 if (kidFrame->IsSubtreeDirty()) { 2932 // The column groups don't care about dimensions or reflow inputs. 2933 ReflowOutput kidSize(wm); 2934 ReflowInput kidReflowInput(presContext, kidFrame, aRenderingContext, 2935 LogicalSize(kidFrame->GetWritingMode())); 2936 nsReflowStatus cgStatus; 2937 const LogicalPoint dummyPos(wm); 2938 const nsSize dummyContainerSize; 2939 ReflowChild(kidFrame, presContext, kidSize, kidReflowInput, wm, 2940 dummyPos, dummyContainerSize, ReflowChildFlags::Default, 2941 cgStatus); 2942 FinishReflowChild(kidFrame, presContext, kidSize, &kidReflowInput, wm, 2943 dummyPos, dummyContainerSize, 2944 ReflowChildFlags::Default); 2945 } 2946 } 2947 SetHaveReflowedColGroups(true); 2948 } 2949 } 2950 2951 nscoord nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput, 2952 const LogicalMargin& aBorderPadding, 2953 const nsReflowStatus& aStatus) { 2954 WritingMode wm = aReflowInput.GetWritingMode(); 2955 2956 RowGroupArray rowGroups = OrderedRowGroups(); 2957 if (rowGroups.IsEmpty()) { 2958 if (eCompatibility_NavQuirks == PresContext()->CompatibilityMode()) { 2959 // empty tables should not have a size in quirks mode 2960 return 0; 2961 } 2962 return CalcBorderBoxBSize(aReflowInput, aBorderPadding, 2963 aBorderPadding.BStartEnd(wm)); 2964 } 2965 2966 nsTableCellMap* cellMap = GetCellMap(); 2967 MOZ_ASSERT(cellMap); 2968 int32_t rowCount = cellMap->GetRowCount(); 2969 int32_t colCount = cellMap->GetColCount(); 2970 nscoord desiredBSize = aBorderPadding.BStartEnd(wm); 2971 if (rowCount > 0 && colCount > 0) { 2972 if (!GetPrevInFlow()) { 2973 desiredBSize += GetRowSpacing(-1); 2974 } 2975 const nsTableRowGroupFrame* lastRG = rowGroups.LastElement(); 2976 for (nsTableRowGroupFrame* rg : rowGroups) { 2977 desiredBSize += rg->BSize(wm); 2978 if (rg != lastRG || aStatus.IsFullyComplete()) { 2979 desiredBSize += 2980 GetRowSpacing(rg->GetStartRowIndex() + rg->GetRowCount()); 2981 } 2982 } 2983 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE && 2984 aStatus.IsIncomplete()) { 2985 desiredBSize = std::max(desiredBSize, aReflowInput.AvailableBSize()); 2986 } 2987 } 2988 2989 // see if a specified table bsize requires dividing additional space to rows 2990 if (!GetPrevInFlow()) { 2991 nscoord bSize = 2992 CalcBorderBoxBSize(aReflowInput, aBorderPadding, desiredBSize); 2993 if (bSize > desiredBSize) { 2994 // proportionately distribute the excess bsize to unconstrained rows in 2995 // each unconstrained row group. 2996 DistributeBSizeToRows(aReflowInput, bSize - desiredBSize); 2997 return bSize; 2998 } 2999 // Tables don't shrink below their intrinsic size, apparently, even when 3000 // constrained by stuff like flex / grid or what not. 3001 return desiredBSize; 3002 } 3003 3004 // FIXME(emilio): Is this right? This only affects fragmented tables... 3005 return desiredBSize; 3006 } 3007 3008 static void ResizeCells(nsTableFrame& aTableFrame) { 3009 nsTableFrame::RowGroupArray rowGroups = aTableFrame.OrderedRowGroups(); 3010 WritingMode wm = aTableFrame.GetWritingMode(); 3011 ReflowOutput tableDesiredSize(wm); 3012 tableDesiredSize.SetSize(wm, aTableFrame.GetLogicalSize(wm)); 3013 tableDesiredSize.SetOverflowAreasToDesiredBounds(); 3014 3015 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 3016 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 3017 3018 ReflowOutput groupDesiredSize(wm); 3019 groupDesiredSize.SetSize(wm, rgFrame->GetLogicalSize(wm)); 3020 groupDesiredSize.SetOverflowAreasToDesiredBounds(); 3021 3022 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); 3023 while (rowFrame) { 3024 rowFrame->DidResize(); 3025 rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame); 3026 rowFrame = rowFrame->GetNextRow(); 3027 } 3028 rgFrame->FinishAndStoreOverflow(&groupDesiredSize); 3029 tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas + 3030 rgFrame->GetPosition()); 3031 } 3032 aTableFrame.FinishAndStoreOverflow(&tableDesiredSize); 3033 } 3034 3035 void nsTableFrame::DistributeBSizeToRows(const ReflowInput& aReflowInput, 3036 nscoord aAmount) { 3037 WritingMode wm = aReflowInput.GetWritingMode(); 3038 LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm); 3039 3040 nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained(); 3041 3042 RowGroupArray rowGroups = OrderedRowGroups(); 3043 3044 nscoord amountUsed = 0; 3045 // distribute space to each pct bsize row whose row group doesn't have a 3046 // computed bsize, and base the pct on the table bsize. If the row group had a 3047 // computed bsize, then this was already done in 3048 // nsTableRowGroupFrame::CalculateRowBSizes 3049 nscoord pctBasis = 3050 aReflowInput.ComputedBSize() - GetRowSpacing(-1, GetRowCount()); 3051 nscoord bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(0); 3052 nscoord bEndRG = bOriginRG; 3053 uint32_t rgIdx; 3054 for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 3055 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 3056 nscoord amountUsedByRG = 0; 3057 nscoord bOriginRow = 0; 3058 const LogicalRect rgNormalRect = 3059 rgFrame->GetLogicalNormalRect(wm, containerSize); 3060 if (!rgFrame->HasStyleBSize()) { 3061 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); 3062 while (rowFrame) { 3063 // We don't know the final width of the rowGroupFrame yet, so use 0,0 3064 // as a dummy containerSize here; we'll adjust the row positions at 3065 // the end, after the rowGroup size is finalized. 3066 const nsSize dummyContainerSize; 3067 const LogicalRect rowNormalRect = 3068 rowFrame->GetLogicalNormalRect(wm, dummyContainerSize); 3069 const nscoord rowSpacing = GetRowSpacing(rowFrame->GetRowIndex()); 3070 if ((amountUsed < aAmount) && rowFrame->HasPctBSize()) { 3071 nscoord pctBSize = rowFrame->GetInitialBSize(pctBasis); 3072 nscoord amountForRow = std::min(aAmount - amountUsed, 3073 pctBSize - rowNormalRect.BSize(wm)); 3074 if (amountForRow > 0) { 3075 // XXXbz we don't need to move the row's b-position to bOriginRow? 3076 nsRect origRowRect = rowFrame->GetRect(); 3077 nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow; 3078 rowFrame->SetSize( 3079 wm, LogicalSize(wm, rowNormalRect.ISize(wm), newRowBSize)); 3080 bOriginRow += newRowBSize + rowSpacing; 3081 bEndRG += newRowBSize + rowSpacing; 3082 amountUsed += amountForRow; 3083 amountUsedByRG += amountForRow; 3084 // rowFrame->DidResize(); 3085 3086 rgFrame->InvalidateFrameWithRect(origRowRect); 3087 rgFrame->InvalidateFrame(); 3088 } 3089 } else { 3090 if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm) && 3091 !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 3092 rowFrame->InvalidateFrameSubtree(); 3093 rowFrame->MovePositionBy( 3094 wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm))); 3095 rowFrame->InvalidateFrameSubtree(); 3096 } 3097 bOriginRow += rowNormalRect.BSize(wm) + rowSpacing; 3098 bEndRG += rowNormalRect.BSize(wm) + rowSpacing; 3099 } 3100 rowFrame = rowFrame->GetNextRow(); 3101 } 3102 if (amountUsed > 0) { 3103 if (rgNormalRect.BStart(wm) != bOriginRG) { 3104 rgFrame->InvalidateFrameSubtree(); 3105 } 3106 3107 nsRect origRgNormalRect = rgFrame->GetRect(); 3108 nsRect origRgInkOverflow = rgFrame->InkOverflowRect(); 3109 3110 rgFrame->MovePositionBy( 3111 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm))); 3112 rgFrame->SetSize(wm, 3113 LogicalSize(wm, rgNormalRect.ISize(wm), 3114 rgNormalRect.BSize(wm) + amountUsedByRG)); 3115 3116 nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect, 3117 origRgInkOverflow, false); 3118 } 3119 } else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) { 3120 rgFrame->InvalidateFrameSubtree(); 3121 rgFrame->MovePositionBy( 3122 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm))); 3123 // Make sure child views are properly positioned 3124 rgFrame->InvalidateFrameSubtree(); 3125 } 3126 bOriginRG = bEndRG; 3127 } 3128 3129 if (amountUsed >= aAmount) { 3130 ResizeCells(*this); 3131 return; 3132 } 3133 3134 // get the first row without a style bsize where its row group has an 3135 // unconstrained bsize 3136 nsTableRowGroupFrame* firstUnStyledRG = nullptr; 3137 nsTableRowFrame* firstUnStyledRow = nullptr; 3138 for (rgIdx = 0; rgIdx < rowGroups.Length() && !firstUnStyledRG; rgIdx++) { 3139 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 3140 if (!rgFrame->HasStyleBSize()) { 3141 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); 3142 while (rowFrame) { 3143 if (!rowFrame->HasStyleBSize()) { 3144 firstUnStyledRG = rgFrame; 3145 firstUnStyledRow = rowFrame; 3146 break; 3147 } 3148 rowFrame = rowFrame->GetNextRow(); 3149 } 3150 } 3151 } 3152 3153 nsTableRowFrame* lastEligibleRow = nullptr; 3154 // Accumulate the correct divisor. This will be the total bsize of all 3155 // unstyled rows inside unstyled row groups, unless there are none, in which 3156 // case, it will be number of all rows. If the unstyled rows don't have a 3157 // bsize, divide the space equally among them. 3158 nscoord divisor = 0; 3159 int32_t eligibleRows = 0; 3160 bool expandEmptyRows = false; 3161 3162 if (!firstUnStyledRow) { 3163 // there is no unstyled row 3164 divisor = GetRowCount(); 3165 } else { 3166 for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 3167 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 3168 if (!firstUnStyledRG || !rgFrame->HasStyleBSize()) { 3169 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); 3170 while (rowFrame) { 3171 if (!firstUnStyledRG || !rowFrame->HasStyleBSize()) { 3172 NS_ASSERTION(rowFrame->BSize(wm) >= 0, 3173 "negative row frame block-size"); 3174 divisor += rowFrame->BSize(wm); 3175 eligibleRows++; 3176 lastEligibleRow = rowFrame; 3177 } 3178 rowFrame = rowFrame->GetNextRow(); 3179 } 3180 } 3181 } 3182 if (divisor <= 0) { 3183 if (eligibleRows > 0) { 3184 expandEmptyRows = true; 3185 } else { 3186 NS_ERROR("invalid divisor"); 3187 return; 3188 } 3189 } 3190 } 3191 // allocate the extra bsize to the unstyled row groups and rows 3192 nscoord bSizeToDistribute = aAmount - amountUsed; 3193 bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(-1); 3194 bEndRG = bOriginRG; 3195 for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 3196 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 3197 nscoord amountUsedByRG = 0; 3198 nscoord bOriginRow = 0; 3199 const LogicalRect rgNormalRect = 3200 rgFrame->GetLogicalNormalRect(wm, containerSize); 3201 nsRect rgInkOverflow = rgFrame->InkOverflowRect(); 3202 // see if there is an eligible row group or we distribute to all rows 3203 if (!firstUnStyledRG || !rgFrame->HasStyleBSize() || !eligibleRows) { 3204 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame; 3205 rowFrame = rowFrame->GetNextRow()) { 3206 const nscoord rowSpacing = GetRowSpacing(rowFrame->GetRowIndex()); 3207 // We don't know the final width of the rowGroupFrame yet, so use 0,0 3208 // as a dummy containerSize here; we'll adjust the row positions at 3209 // the end, after the rowGroup size is finalized. 3210 const nsSize dummyContainerSize; 3211 const LogicalRect rowNormalRect = 3212 rowFrame->GetLogicalNormalRect(wm, dummyContainerSize); 3213 nsRect rowInkOverflow = rowFrame->InkOverflowRect(); 3214 // see if there is an eligible row or we distribute to all rows 3215 if (!firstUnStyledRow || !rowFrame->HasStyleBSize() || !eligibleRows) { 3216 float ratio; 3217 if (eligibleRows) { 3218 if (!expandEmptyRows) { 3219 // The amount of additional space each row gets is proportional 3220 // to its bsize 3221 ratio = float(rowNormalRect.BSize(wm)) / float(divisor); 3222 } else { 3223 // empty rows get all the same additional space 3224 ratio = 1.0f / float(eligibleRows); 3225 } 3226 } else { 3227 // all rows get the same additional space 3228 ratio = 1.0f / float(divisor); 3229 } 3230 // give rows their additional space, except for the last row which 3231 // gets the remainder 3232 nscoord amountForRow = 3233 (rowFrame == lastEligibleRow) 3234 ? aAmount - amountUsed 3235 : NSToCoordRound(((float)(bSizeToDistribute)) * ratio); 3236 amountForRow = std::min(amountForRow, aAmount - amountUsed); 3237 3238 if (bOriginRow != rowNormalRect.BStart(wm)) { 3239 rowFrame->InvalidateFrameSubtree(); 3240 } 3241 3242 // update the row bsize 3243 nsRect origRowRect = rowFrame->GetRect(); 3244 nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow; 3245 rowFrame->MovePositionBy( 3246 wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm))); 3247 rowFrame->SetSize( 3248 wm, LogicalSize(wm, rowNormalRect.ISize(wm), newRowBSize)); 3249 3250 bOriginRow += newRowBSize + rowSpacing; 3251 bEndRG += newRowBSize + rowSpacing; 3252 3253 amountUsed += amountForRow; 3254 amountUsedByRG += amountForRow; 3255 NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation"); 3256 // rowFrame->DidResize(); 3257 nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect, 3258 rowInkOverflow, false); 3259 } else { 3260 if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm)) { 3261 rowFrame->InvalidateFrameSubtree(); 3262 rowFrame->MovePositionBy( 3263 wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm))); 3264 rowFrame->InvalidateFrameSubtree(); 3265 } 3266 bOriginRow += rowNormalRect.BSize(wm) + rowSpacing; 3267 bEndRG += rowNormalRect.BSize(wm) + rowSpacing; 3268 } 3269 } 3270 3271 if (amountUsed > 0) { 3272 if (rgNormalRect.BStart(wm) != bOriginRG) { 3273 rgFrame->InvalidateFrameSubtree(); 3274 } 3275 3276 nsRect origRgNormalRect = rgFrame->GetRect(); 3277 rgFrame->MovePositionBy( 3278 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm))); 3279 rgFrame->SetSize(wm, 3280 LogicalSize(wm, rgNormalRect.ISize(wm), 3281 rgNormalRect.BSize(wm) + amountUsedByRG)); 3282 3283 nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect, 3284 rgInkOverflow, false); 3285 } 3286 3287 // For vertical-rl mode, we needed to position the rows relative to the 3288 // right-hand (block-start) side of the group; but we couldn't do that 3289 // above, as we didn't know the rowGroupFrame's final block size yet. 3290 // So we used a dummyContainerSize of 0,0 earlier, placing the rows to 3291 // the left of the rowGroupFrame's (physical) origin. Now we move them 3292 // all rightwards by its final width. 3293 if (wm.IsVerticalRL()) { 3294 nscoord rgWidth = rgFrame->GetSize().width; 3295 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame; 3296 rowFrame = rowFrame->GetNextRow()) { 3297 rowFrame->InvalidateFrameSubtree(); 3298 rowFrame->MovePositionBy(nsPoint(rgWidth, 0)); 3299 rowFrame->InvalidateFrameSubtree(); 3300 } 3301 } 3302 } else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) { 3303 rgFrame->InvalidateFrameSubtree(); 3304 rgFrame->MovePositionBy( 3305 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm))); 3306 // Make sure child views are properly positioned 3307 rgFrame->InvalidateFrameSubtree(); 3308 } 3309 bOriginRG = bEndRG; 3310 } 3311 3312 ResizeCells(*this); 3313 } 3314 3315 nscoord nsTableFrame::GetColumnISizeFromFirstInFlow(int32_t aColIndex) { 3316 MOZ_ASSERT(this == FirstInFlow()); 3317 nsTableColFrame* colFrame = GetColFrame(aColIndex); 3318 return colFrame ? colFrame->GetFinalISize() : 0; 3319 } 3320 3321 nscoord nsTableFrame::GetColSpacing() { 3322 if (IsBorderCollapse()) { 3323 return 0; 3324 } 3325 return StyleTableBorder()->mBorderSpacing.width.ToAppUnits(); 3326 } 3327 3328 // XXX: could cache this. But be sure to check style changes if you do! 3329 nscoord nsTableFrame::GetColSpacing(int32_t aColIndex) { 3330 NS_ASSERTION(aColIndex >= -1 && aColIndex <= GetColCount(), 3331 "Column index exceeds the bounds of the table"); 3332 // Index is irrelevant for ordinary tables. We check that it falls within 3333 // appropriate bounds to increase confidence of correctness in situations 3334 // where it does matter. 3335 return GetColSpacing(); 3336 } 3337 3338 nscoord nsTableFrame::GetColSpacing(int32_t aStartColIndex, 3339 int32_t aEndColIndex) { 3340 NS_ASSERTION(aStartColIndex >= -1 && aStartColIndex <= GetColCount(), 3341 "Start column index exceeds the bounds of the table"); 3342 NS_ASSERTION(aEndColIndex >= -1 && aEndColIndex <= GetColCount(), 3343 "End column index exceeds the bounds of the table"); 3344 NS_ASSERTION(aStartColIndex <= aEndColIndex, 3345 "End index must not be less than start index"); 3346 // Only one possible value so just multiply it out. Tables where index 3347 // matters will override this function 3348 return GetColSpacing() * (aEndColIndex - aStartColIndex); 3349 } 3350 3351 nscoord nsTableFrame::GetRowSpacing() { 3352 if (IsBorderCollapse()) { 3353 return 0; 3354 } 3355 return StyleTableBorder()->mBorderSpacing.height.ToAppUnits(); 3356 } 3357 3358 // XXX: could cache this. But be sure to check style changes if you do! 3359 nscoord nsTableFrame::GetRowSpacing(int32_t aRowIndex) { 3360 NS_ASSERTION(aRowIndex >= -1 && aRowIndex <= GetRowCount(), 3361 "Row index exceeds the bounds of the table"); 3362 // Index is irrelevant for ordinary tables. We check that it falls within 3363 // appropriate bounds to increase confidence of correctness in situations 3364 // where it does matter. 3365 return GetRowSpacing(); 3366 } 3367 3368 nscoord nsTableFrame::GetRowSpacing(int32_t aStartRowIndex, 3369 int32_t aEndRowIndex) { 3370 NS_ASSERTION(aStartRowIndex >= -1 && aStartRowIndex <= GetRowCount(), 3371 "Start row index exceeds the bounds of the table"); 3372 NS_ASSERTION(aEndRowIndex >= -1 && aEndRowIndex <= GetRowCount(), 3373 "End row index exceeds the bounds of the table"); 3374 NS_ASSERTION(aStartRowIndex <= aEndRowIndex, 3375 "End index must not be less than start index"); 3376 // Only one possible value so just multiply it out. Tables where index 3377 // matters will override this function 3378 return GetRowSpacing() * (aEndRowIndex - aStartRowIndex); 3379 } 3380 3381 nscoord nsTableFrame::SynthesizeFallbackBaseline( 3382 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { 3383 if (aBaselineGroup == BaselineSharingGroup::Last) { 3384 return 0; 3385 } 3386 return BSize(aWM); 3387 } 3388 3389 /* virtual */ 3390 Maybe<nscoord> nsTableFrame::GetNaturalBaselineBOffset( 3391 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 3392 BaselineExportContext) const { 3393 if (StyleDisplay()->IsContainLayout()) { 3394 return Nothing{}; 3395 } 3396 3397 RowGroupArray orderedRowGroups = OrderedRowGroups(); 3398 // XXX not sure if this should be the size of the containing block instead. 3399 nsSize containerSize = mRect.Size(); 3400 auto TableBaseline = [aWM, containerSize]( 3401 nsTableRowGroupFrame* aRowGroup, 3402 nsTableRowFrame* aRow) -> Maybe<nscoord> { 3403 const nscoord rgBStart = 3404 aRowGroup->GetLogicalNormalRect(aWM, containerSize).BStart(aWM); 3405 const nscoord rowBStart = 3406 aRow->GetLogicalNormalRect(aWM, aRowGroup->GetSize()).BStart(aWM); 3407 return aRow->GetRowBaseline(aWM).map( 3408 [rgBStart, rowBStart](nscoord aBaseline) { 3409 return rgBStart + rowBStart + aBaseline; 3410 }); 3411 }; 3412 if (aBaselineGroup == BaselineSharingGroup::First) { 3413 for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { 3414 nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; 3415 nsTableRowFrame* row = rgFrame->GetFirstRow(); 3416 if (row) { 3417 return TableBaseline(rgFrame, row); 3418 } 3419 } 3420 } else { 3421 for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) { 3422 nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; 3423 nsTableRowFrame* row = rgFrame->GetLastRow(); 3424 if (row) { 3425 return TableBaseline(rgFrame, row).map([this, aWM](nscoord aBaseline) { 3426 return BSize(aWM) - aBaseline; 3427 }); 3428 } 3429 } 3430 } 3431 return Nothing{}; 3432 } 3433 3434 /* ----- global methods ----- */ 3435 3436 nsTableFrame* NS_NewTableFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 3437 return new (aPresShell) nsTableFrame(aStyle, aPresShell->GetPresContext()); 3438 } 3439 3440 NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame) 3441 3442 nsTableFrame* nsTableFrame::GetTableFrame(nsIFrame* aFrame) { 3443 for (nsIFrame* ancestor = aFrame->GetParent(); ancestor; 3444 ancestor = ancestor->GetParent()) { 3445 if (ancestor->IsTableFrame()) { 3446 return static_cast<nsTableFrame*>(ancestor); 3447 } 3448 } 3449 MOZ_CRASH("unable to find table parent"); 3450 return nullptr; 3451 } 3452 3453 bool nsTableFrame::IsAutoBSize(WritingMode aWM) { 3454 const auto bsize = 3455 StylePosition()->BSize(aWM, AnchorPosResolutionParams::From(this)); 3456 if (bsize->IsAuto()) { 3457 return true; 3458 } 3459 return bsize->ConvertsToPercentage() && bsize->ToPercentage() <= 0.0f; 3460 } 3461 3462 nscoord nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aReflowInput, 3463 const LogicalMargin& aBorderPadding, 3464 nscoord aIntrinsicBorderBoxBSize) { 3465 WritingMode wm = aReflowInput.GetWritingMode(); 3466 nscoord bSize = aReflowInput.ComputedBSize(); 3467 nscoord bp = aBorderPadding.BStartEnd(wm); 3468 if (bSize == NS_UNCONSTRAINEDSIZE) { 3469 if (aIntrinsicBorderBoxBSize == NS_UNCONSTRAINEDSIZE) { 3470 return NS_UNCONSTRAINEDSIZE; 3471 } 3472 bSize = std::max(0, aIntrinsicBorderBoxBSize - bp); 3473 } 3474 return aReflowInput.ApplyMinMaxBSize(bSize) + bp; 3475 } 3476 3477 bool nsTableFrame::IsAutoLayout() { 3478 if (StyleTable()->mLayoutStrategy == StyleTableLayout::Auto) { 3479 return true; 3480 } 3481 // a fixed-layout inline-table must have a inline size 3482 // and tables with inline size set to 'max-content' must be 3483 // auto-layout (at least as long as 3484 // FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX) 3485 const auto iSize = StylePosition()->ISize( 3486 GetWritingMode(), AnchorPosResolutionParams::From(this)); 3487 return iSize->IsAuto() || iSize->IsMaxContent(); 3488 } 3489 3490 #ifdef DEBUG_FRAME_DUMP 3491 nsresult nsTableFrame::GetFrameName(nsAString& aResult) const { 3492 return MakeFrameName(u"Table"_ns, aResult); 3493 } 3494 #endif 3495 3496 // Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) 3497 // that is of type aChildType 3498 nsIFrame* nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame, 3499 nsIFrame* aPriorChildFrame, 3500 LayoutFrameType aChildType) { 3501 nsIFrame* result = nullptr; 3502 if (!aPriorChildFrame) { 3503 return result; 3504 } 3505 if (aChildType == aPriorChildFrame->Type()) { 3506 return aPriorChildFrame; 3507 } 3508 3509 // aPriorChildFrame is not of type aChildType, so we need start from 3510 // the beginnng and find the closest one 3511 nsIFrame* lastMatchingFrame = nullptr; 3512 nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild(); 3513 while (childFrame && (childFrame != aPriorChildFrame)) { 3514 if (aChildType == childFrame->Type()) { 3515 lastMatchingFrame = childFrame; 3516 } 3517 childFrame = childFrame->GetNextSibling(); 3518 } 3519 return lastMatchingFrame; 3520 } 3521 3522 #ifdef DEBUG 3523 void nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame) { 3524 if (!aKidFrame) return; 3525 3526 for (nsIFrame* cFrame : aKidFrame->PrincipalChildList()) { 3527 nsTableRowFrame* rowFrame = do_QueryFrame(cFrame); 3528 if (rowFrame) { 3529 printf("row(%d)=%p ", rowFrame->GetRowIndex(), 3530 static_cast<void*>(rowFrame)); 3531 for (nsIFrame* childFrame : cFrame->PrincipalChildList()) { 3532 nsTableCellFrame* cellFrame = do_QueryFrame(childFrame); 3533 if (cellFrame) { 3534 uint32_t colIndex = cellFrame->ColIndex(); 3535 printf("cell(%u)=%p ", colIndex, static_cast<void*>(childFrame)); 3536 } 3537 } 3538 printf("\n"); 3539 } else { 3540 DumpRowGroup(rowFrame); 3541 } 3542 } 3543 } 3544 3545 void nsTableFrame::Dump(bool aDumpRows, bool aDumpCols, bool aDumpCellMap) { 3546 printf("***START TABLE DUMP*** \n"); 3547 // dump the columns widths array 3548 printf("mColWidths="); 3549 int32_t numCols = GetColCount(); 3550 int32_t colIdx; 3551 nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow()); 3552 for (colIdx = 0; colIdx < numCols; colIdx++) { 3553 printf("%d ", fif->GetColumnISizeFromFirstInFlow(colIdx)); 3554 } 3555 printf("\n"); 3556 3557 if (aDumpRows) { 3558 nsIFrame* kidFrame = mFrames.FirstChild(); 3559 while (kidFrame) { 3560 DumpRowGroup(kidFrame); 3561 kidFrame = kidFrame->GetNextSibling(); 3562 } 3563 } 3564 3565 if (aDumpCols) { 3566 // output col frame cache 3567 printf("\n col frame cache ->"); 3568 for (colIdx = 0; colIdx < numCols; colIdx++) { 3569 nsTableColFrame* colFrame = mColFrames.ElementAt(colIdx); 3570 if (0 == (colIdx % 8)) { 3571 printf("\n"); 3572 } 3573 printf("%d=%p ", colIdx, static_cast<void*>(colFrame)); 3574 nsTableColType colType = colFrame->GetColType(); 3575 switch (colType) { 3576 case eColContent: 3577 printf(" content "); 3578 break; 3579 case eColAnonymousCol: 3580 printf(" anonymous-column "); 3581 break; 3582 case eColAnonymousColGroup: 3583 printf(" anonymous-colgroup "); 3584 break; 3585 case eColAnonymousCell: 3586 printf(" anonymous-cell "); 3587 break; 3588 } 3589 } 3590 printf("\n colgroups->"); 3591 for (nsIFrame* childFrame : mColGroups) { 3592 if (LayoutFrameType::TableColGroup == childFrame->Type()) { 3593 nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)childFrame; 3594 colGroupFrame->Dump(1); 3595 } 3596 } 3597 for (colIdx = 0; colIdx < numCols; colIdx++) { 3598 printf("\n"); 3599 nsTableColFrame* colFrame = GetColFrame(colIdx); 3600 colFrame->Dump(1); 3601 } 3602 } 3603 if (aDumpCellMap) { 3604 nsTableCellMap* cellMap = GetCellMap(); 3605 cellMap->Dump(); 3606 } 3607 printf(" ***END TABLE DUMP*** \n"); 3608 } 3609 #endif 3610 3611 bool nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const { 3612 if (aColIndex == 0) { 3613 return true; 3614 } 3615 // Since fixed-layout tables should not have their column sizes change 3616 // as they load, we assume that all columns are significant. 3617 auto* fif = static_cast<nsTableFrame*>(FirstInFlow()); 3618 if (fif->LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed) { 3619 return true; 3620 } 3621 nsTableCellMap* cellMap = fif->GetCellMap(); 3622 if (!cellMap) { 3623 return false; 3624 } 3625 if (cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0) { 3626 return true; 3627 } 3628 // Check if we have a <col> element with a non-zero definite inline size. 3629 // Note: percentages and calc(%) are intentionally not considered. 3630 if (const auto* col = fif->GetColFrame(aColIndex)) { 3631 const auto anchorResolutionParams = AnchorPosResolutionParams::From(col); 3632 const auto iSize = 3633 col->StylePosition()->ISize(GetWritingMode(), anchorResolutionParams); 3634 if (iSize->ConvertsToLength() && iSize->ToLength() > 0) { 3635 const auto maxISize = col->StylePosition()->MaxISize( 3636 GetWritingMode(), anchorResolutionParams); 3637 if (!maxISize->ConvertsToLength() || maxISize->ToLength() > 0) { 3638 return true; 3639 } 3640 } 3641 const auto minISize = col->StylePosition()->MinISize( 3642 GetWritingMode(), anchorResolutionParams); 3643 if (minISize->ConvertsToLength() && minISize->ToLength() > 0) { 3644 return true; 3645 } 3646 } 3647 return false; 3648 } 3649 3650 /******************************************************************************** 3651 * Collapsing Borders 3652 * 3653 * The CSS spec says to resolve border conflicts in this order: 3654 * 1) any border with the style HIDDEN wins 3655 * 2) the widest border with a style that is not NONE wins 3656 * 3) the border styles are ranked in this order, highest to lowest precedence: 3657 * double, solid, dashed, dotted, ridge, outset, groove, inset 3658 * 4) borders that are of equal width and style (differ only in color) have 3659 * this precedence: cell, row, rowgroup, col, colgroup, table 3660 * 5) if all border styles are NONE, then that's the computed border style. 3661 *******************************************************************************/ 3662 3663 #ifdef DEBUG 3664 # define VerifyNonNegativeDamageRect(r) \ 3665 NS_ASSERTION((r).StartCol() >= 0, "negative col index"); \ 3666 NS_ASSERTION((r).StartRow() >= 0, "negative row index"); \ 3667 NS_ASSERTION((r).ColCount() >= 0, "negative cols damage"); \ 3668 NS_ASSERTION((r).RowCount() >= 0, "negative rows damage"); 3669 # define VerifyDamageRect(r) \ 3670 VerifyNonNegativeDamageRect(r); \ 3671 NS_ASSERTION((r).EndCol() <= GetColCount(), \ 3672 "cols damage extends outside table"); \ 3673 NS_ASSERTION((r).EndRow() <= GetRowCount(), \ 3674 "rows damage extends outside table"); 3675 #endif 3676 3677 void nsTableFrame::AddBCDamageArea(const TableArea& aValue) { 3678 MOZ_ASSERT(IsBorderCollapse(), 3679 "Why call this if we are not border-collapsed?"); 3680 #ifdef DEBUG 3681 VerifyDamageRect(aValue); 3682 #endif 3683 3684 SetNeedToCalcBCBorders(true); 3685 SetNeedToCalcHasBCBorders(true); 3686 // Get the property 3687 TableBCData* value = GetOrCreateTableBCData(); 3688 3689 #ifdef DEBUG 3690 VerifyNonNegativeDamageRect(value->mDamageArea); 3691 #endif 3692 // Clamp the old damage area to the current table area in case it shrunk. 3693 int32_t cols = GetColCount(); 3694 if (value->mDamageArea.EndCol() > cols) { 3695 if (value->mDamageArea.StartCol() > cols) { 3696 value->mDamageArea.StartCol() = cols; 3697 value->mDamageArea.ColCount() = 0; 3698 } else { 3699 value->mDamageArea.ColCount() = cols - value->mDamageArea.StartCol(); 3700 } 3701 } 3702 int32_t rows = GetRowCount(); 3703 if (value->mDamageArea.EndRow() > rows) { 3704 if (value->mDamageArea.StartRow() > rows) { 3705 value->mDamageArea.StartRow() = rows; 3706 value->mDamageArea.RowCount() = 0; 3707 } else { 3708 value->mDamageArea.RowCount() = rows - value->mDamageArea.StartRow(); 3709 } 3710 } 3711 3712 // Construct a union of the new and old damage areas. 3713 value->mDamageArea.UnionArea(value->mDamageArea, aValue); 3714 } 3715 3716 void nsTableFrame::SetFullBCDamageArea() { 3717 MOZ_ASSERT(IsBorderCollapse(), 3718 "Why call this if we are not border-collapsed?"); 3719 3720 SetNeedToCalcBCBorders(true); 3721 SetNeedToCalcHasBCBorders(true); 3722 3723 TableBCData* value = GetOrCreateTableBCData(); 3724 value->mDamageArea = TableArea(0, 0, GetColCount(), GetRowCount()); 3725 } 3726 3727 /* BCCellBorder represents a border segment which can be either an inline-dir 3728 * or a block-dir segment. For each segment we need to know the color, width, 3729 * style, who owns it and how long it is in cellmap coordinates. 3730 * Ownership of these segments is important to calculate which corners should 3731 * be bevelled. This structure has dual use, its used first to compute the 3732 * dominant border for inline-dir and block-dir segments and to store the 3733 * preliminary computed border results in the BCCellBorders structure. 3734 * This temporary storage is not symmetric with respect to inline-dir and 3735 * block-dir border segments, its always column oriented. For each column in 3736 * the cellmap there is a temporary stored block-dir and inline-dir segment. 3737 * XXX_Bernd this asymmetry is the root of those rowspan bc border errors 3738 */ 3739 struct BCCellBorder { 3740 BCCellBorder() { Reset(0, 1); } 3741 void Reset(uint32_t aRowIndex, uint32_t aRowSpan); 3742 nscolor color; // border segment color 3743 nscoord width; // border segment width 3744 StyleBorderStyle style; // border segment style, possible values are defined 3745 // in nsStyleConsts.h as StyleBorderStyle::* 3746 BCBorderOwner owner; // border segment owner, possible values are defined 3747 // in celldata.h. In the cellmap for each border 3748 // segment we store the owner and later when 3749 // painting we know the owner and can retrieve the 3750 // style info from the corresponding frame 3751 int32_t rowIndex; // rowIndex of temporary stored inline-dir border 3752 // segments relative to the table 3753 int32_t rowSpan; // row span of temporary stored inline-dir border 3754 // segments 3755 }; 3756 3757 void BCCellBorder::Reset(uint32_t aRowIndex, uint32_t aRowSpan) { 3758 style = StyleBorderStyle::None; 3759 color = 0; 3760 width = 0; 3761 owner = eTableOwner; 3762 rowIndex = aRowIndex; 3763 rowSpan = aRowSpan; 3764 } 3765 3766 class BCMapCellIterator; 3767 3768 /***************************************************************** 3769 * BCMapCellInfo 3770 * This structure stores information during the computation of winning borders 3771 * in CalcBCBorders, so that they don't need to be looked up repeatedly. 3772 ****************************************************************/ 3773 struct BCMapCellInfo final { 3774 explicit BCMapCellInfo(nsTableFrame* aTableFrame); 3775 void ResetCellInfo(); 3776 void SetInfo(nsTableRowFrame* aNewRow, int32_t aColIndex, 3777 BCCellData* aCellData, BCMapCellIterator* aIter, 3778 nsCellMap* aCellMap = nullptr); 3779 3780 // Functions to (re)set the border widths on the table related cell frames, 3781 // where the knowledge about the current position in the table is used. 3782 // For most "normal" cells that have row/colspan of 1, these functions 3783 // are called once at most during the reflow, setting the value as given 3784 // (Discarding the value from the previous reflow, which is now irrelevant). 3785 // However, for cells spanning multiple rows/coluns, the maximum border 3786 // width seen is stored. This is controlled by calling the reset functions 3787 // before the cell's border is computed the first time. 3788 void ResetIStartBorderWidths(); 3789 void ResetIEndBorderWidths(); 3790 void ResetBStartBorderWidths(); 3791 void ResetBEndBorderWidths(); 3792 3793 void SetIStartBorderWidths(nscoord aWidth); 3794 void SetIEndBorderWidths(nscoord aWidth); 3795 void SetBStartBorderWidths(nscoord aWidth); 3796 void SetBEndBorderWidths(nscoord aWidth); 3797 3798 // functions to compute the borders; they depend on the 3799 // knowledge about the current position in the table. The edge functions 3800 // should be called if a table edge is involved, otherwise the internal 3801 // functions should be called. 3802 BCCellBorder GetBStartEdgeBorder(); 3803 BCCellBorder GetBEndEdgeBorder(); 3804 BCCellBorder GetIStartEdgeBorder(); 3805 BCCellBorder GetIEndEdgeBorder(); 3806 BCCellBorder GetIEndInternalBorder(); 3807 BCCellBorder GetIStartInternalBorder(); 3808 BCCellBorder GetBStartInternalBorder(); 3809 BCCellBorder GetBEndInternalBorder(); 3810 3811 // functions to set the internal position information 3812 void SetColumn(int32_t aColX); 3813 // Increment the row as we loop over the rows of a rowspan 3814 void IncrementRow(bool aResetToBStartRowOfCell = false); 3815 3816 // Helper functions to get extent of the cell 3817 int32_t GetCellEndRowIndex() const; 3818 int32_t GetCellEndColIndex() const; 3819 3820 // Storage of table information required to compute individual cell 3821 // information. 3822 nsTableFrame* mTableFrame; 3823 nsTableFrame* mTableFirstInFlow; 3824 int32_t mNumTableRows; 3825 int32_t mNumTableCols; 3826 WritingMode mTableWM; 3827 3828 // a cell can only belong to one rowgroup 3829 nsTableRowGroupFrame* mRowGroup; 3830 3831 // a cell with a rowspan has a bstart and a bend row, and rows in between 3832 nsTableRowFrame* mStartRow; 3833 nsTableRowFrame* mEndRow; 3834 nsTableRowFrame* mCurrentRowFrame; 3835 3836 // a cell with a colspan has an istart and iend column and columns in between 3837 // they can belong to different colgroups 3838 nsTableColGroupFrame* mColGroup; 3839 nsTableColGroupFrame* mCurrentColGroupFrame; 3840 3841 nsTableColFrame* mStartCol; 3842 nsTableColFrame* mEndCol; 3843 nsTableColFrame* mCurrentColFrame; 3844 3845 // cell information 3846 BCCellData* mCellData; 3847 nsBCTableCellFrame* mCell; 3848 3849 int32_t mRowIndex; 3850 int32_t mRowSpan; 3851 int32_t mColIndex; 3852 int32_t mColSpan; 3853 3854 // flags to describe the position of the cell with respect to the row- and 3855 // colgroups, for instance mRgAtStart documents that the bStart cell border 3856 // hits a rowgroup border 3857 bool mRgAtStart; 3858 bool mRgAtEnd; 3859 bool mCgAtStart; 3860 bool mCgAtEnd; 3861 }; 3862 3863 BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame) 3864 : mTableFrame(aTableFrame), 3865 mTableFirstInFlow(static_cast<nsTableFrame*>(aTableFrame->FirstInFlow())), 3866 mNumTableRows(aTableFrame->GetRowCount()), 3867 mNumTableCols(aTableFrame->GetColCount()), 3868 mTableWM(aTableFrame->Style()), 3869 mCurrentRowFrame(nullptr), 3870 mCurrentColGroupFrame(nullptr), 3871 mCurrentColFrame(nullptr) { 3872 ResetCellInfo(); 3873 } 3874 3875 void BCMapCellInfo::ResetCellInfo() { 3876 mCellData = nullptr; 3877 mRowGroup = nullptr; 3878 mStartRow = nullptr; 3879 mEndRow = nullptr; 3880 mColGroup = nullptr; 3881 mStartCol = nullptr; 3882 mEndCol = nullptr; 3883 mCell = nullptr; 3884 mRowIndex = mRowSpan = mColIndex = mColSpan = 0; 3885 mRgAtStart = mRgAtEnd = mCgAtStart = mCgAtEnd = false; 3886 } 3887 3888 inline int32_t BCMapCellInfo::GetCellEndRowIndex() const { 3889 return mRowIndex + mRowSpan - 1; 3890 } 3891 3892 inline int32_t BCMapCellInfo::GetCellEndColIndex() const { 3893 return mColIndex + mColSpan - 1; 3894 } 3895 3896 static TableBCData* GetTableBCData(nsTableFrame* aTableFrame) { 3897 auto* firstInFlow = static_cast<nsTableFrame*>(aTableFrame->FirstInFlow()); 3898 return firstInFlow->GetTableBCData(); 3899 } 3900 3901 /***************************************************************** 3902 * BCMapTableInfo 3903 * This structure stores controls border information global to the 3904 * table computed during the border-collapsed border calcuation. 3905 ****************************************************************/ 3906 struct BCMapTableInfo final { 3907 explicit BCMapTableInfo(nsTableFrame* aTableFrame) 3908 : mTableBCData{GetTableBCData(aTableFrame)} {} 3909 3910 void ResetTableIStartBorderWidth() { mTableBCData->mIStartBorderWidth = 0; } 3911 3912 void ResetTableIEndBorderWidth() { mTableBCData->mIEndBorderWidth = 0; } 3913 3914 void ResetTableBStartBorderWidth() { mTableBCData->mBStartBorderWidth = 0; } 3915 3916 void ResetTableBEndBorderWidth() { mTableBCData->mBEndBorderWidth = 0; } 3917 3918 void SetTableIStartBorderWidth(nscoord aWidth); 3919 void SetTableIEndBorderWidth(nscoord aWidth); 3920 void SetTableBStartBorderWidth(nscoord aWidth); 3921 void SetTableBEndBorderWidth(nscoord aWidth); 3922 3923 TableBCData* mTableBCData; 3924 }; 3925 3926 class BCMapCellIterator { 3927 public: 3928 BCMapCellIterator(nsTableFrame* aTableFrame, const TableArea& aDamageArea); 3929 3930 void First(BCMapCellInfo& aMapInfo); 3931 3932 void Next(BCMapCellInfo& aMapInfo); 3933 3934 void PeekIEnd(const BCMapCellInfo& aRefInfo, int32_t aRowIndex, 3935 BCMapCellInfo& aAjaInfo); 3936 3937 void PeekBEnd(const BCMapCellInfo& aRefInfo, int32_t aColIndex, 3938 BCMapCellInfo& aAjaInfo); 3939 3940 void PeekIStart(const BCMapCellInfo& aRefInfo, int32_t aRowIndex, 3941 BCMapCellInfo& aAjaInfo); 3942 3943 bool IsNewRow() { return mIsNewRow; } 3944 3945 nsTableRowFrame* GetPrevRow() const { return mPrevRow; } 3946 nsTableRowFrame* GetCurrentRow() const { return mRow; } 3947 nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup; } 3948 3949 int32_t mRowGroupStart; 3950 int32_t mRowGroupEnd; 3951 bool mAtEnd; 3952 nsCellMap* mCellMap; 3953 3954 private: 3955 bool SetNewRow(nsTableRowFrame* row = nullptr); 3956 bool SetNewRowGroup(bool aFindFirstDamagedRow); 3957 void PeekIAt(const BCMapCellInfo& aRefInfo, int32_t aRowIndex, 3958 int32_t aColIndex, BCMapCellInfo& aAjaInfo); 3959 3960 nsTableFrame* mTableFrame; 3961 nsTableCellMap* mTableCellMap; 3962 nsTableFrame::RowGroupArray mRowGroups; 3963 nsTableRowGroupFrame* mRowGroup; 3964 int32_t mRowGroupIndex; 3965 uint32_t mNumTableRows; 3966 nsTableRowFrame* mRow; 3967 nsTableRowFrame* mPrevRow; 3968 bool mIsNewRow; 3969 int32_t mRowIndex; 3970 uint32_t mNumTableCols; 3971 int32_t mColIndex; 3972 // We don't necessarily want to traverse all areas 3973 // of the table - mArea(Start|End) specify the area to traverse. 3974 // TODO(dshin): Should be not abuse `nsPoint` for this - See bug 1879847. 3975 nsPoint mAreaStart; 3976 nsPoint mAreaEnd; 3977 }; 3978 3979 BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame, 3980 const TableArea& aDamageArea) 3981 : mRowGroupStart(0), 3982 mRowGroupEnd(0), 3983 mCellMap(nullptr), 3984 mTableFrame(aTableFrame), 3985 mRowGroups(aTableFrame->OrderedRowGroups()), 3986 mRowGroup(nullptr), 3987 mPrevRow(nullptr), 3988 mIsNewRow(false) { 3989 mTableCellMap = aTableFrame->GetCellMap(); 3990 3991 mAreaStart.x = aDamageArea.StartCol(); 3992 mAreaStart.y = aDamageArea.StartRow(); 3993 mAreaEnd.x = aDamageArea.EndCol() - 1; 3994 mAreaEnd.y = aDamageArea.EndRow() - 1; 3995 3996 mNumTableRows = mTableFrame->GetRowCount(); 3997 mRow = nullptr; 3998 mRowIndex = 0; 3999 mNumTableCols = mTableFrame->GetColCount(); 4000 mColIndex = 0; 4001 mRowGroupIndex = -1; 4002 4003 mAtEnd = true; // gets reset when First() is called 4004 } 4005 4006 // fill fields that we need for border collapse computation on a given cell 4007 void BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow, int32_t aColIndex, 4008 BCCellData* aCellData, BCMapCellIterator* aIter, 4009 nsCellMap* aCellMap) { 4010 // fill the cell information 4011 mCellData = aCellData; 4012 mColIndex = aColIndex; 4013 4014 // initialize the row information if it was not previously set for cells in 4015 // this row 4016 mRowIndex = 0; 4017 if (aNewRow) { 4018 mStartRow = aNewRow; 4019 mRowIndex = aNewRow->GetRowIndex(); 4020 } 4021 4022 // fill cell frame info and row information 4023 mCell = nullptr; 4024 mRowSpan = 1; 4025 mColSpan = 1; 4026 if (aCellData) { 4027 mCell = static_cast<nsBCTableCellFrame*>(aCellData->GetCellFrame()); 4028 if (mCell) { 4029 if (!mStartRow) { 4030 mStartRow = mCell->GetTableRowFrame(); 4031 if (!mStartRow) ABORT0(); 4032 mRowIndex = mStartRow->GetRowIndex(); 4033 } 4034 mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap); 4035 mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap); 4036 } 4037 } 4038 4039 if (!mStartRow) { 4040 mStartRow = aIter->GetCurrentRow(); 4041 } 4042 if (1 == mRowSpan) { 4043 mEndRow = mStartRow; 4044 } else { 4045 mEndRow = mStartRow->GetNextRow(); 4046 if (mEndRow) { 4047 for (int32_t span = 2; mEndRow && span < mRowSpan; span++) { 4048 mEndRow = mEndRow->GetNextRow(); 4049 } 4050 NS_ASSERTION(mEndRow, "spanned row not found"); 4051 } else { 4052 NS_ERROR("error in cell map"); 4053 mRowSpan = 1; 4054 mEndRow = mStartRow; 4055 } 4056 } 4057 // row group frame info 4058 // try to reuse the rgStart and rgEnd from the iterator as calls to 4059 // GetRowCount() are computationally expensive and should be avoided if 4060 // possible 4061 uint32_t rgStart = aIter->mRowGroupStart; 4062 uint32_t rgEnd = aIter->mRowGroupEnd; 4063 mRowGroup = mStartRow->GetTableRowGroupFrame(); 4064 if (mRowGroup != aIter->GetCurrentRowGroup()) { 4065 rgStart = mRowGroup->GetStartRowIndex(); 4066 rgEnd = rgStart + mRowGroup->GetRowCount() - 1; 4067 } 4068 uint32_t rowIndex = mStartRow->GetRowIndex(); 4069 mRgAtStart = rgStart == rowIndex; 4070 mRgAtEnd = rgEnd == rowIndex + mRowSpan - 1; 4071 4072 // col frame info 4073 mStartCol = mTableFirstInFlow->GetColFrame(aColIndex); 4074 if (!mStartCol) ABORT0(); 4075 4076 mEndCol = mStartCol; 4077 if (mColSpan > 1) { 4078 nsTableColFrame* colFrame = 4079 mTableFirstInFlow->GetColFrame(aColIndex + mColSpan - 1); 4080 if (!colFrame) ABORT0(); 4081 mEndCol = colFrame; 4082 } 4083 4084 // col group frame info 4085 mColGroup = mStartCol->GetTableColGroupFrame(); 4086 int32_t cgStart = mColGroup->GetStartColumnIndex(); 4087 int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1); 4088 mCgAtStart = cgStart == aColIndex; 4089 mCgAtEnd = cgEnd == aColIndex + mColSpan - 1; 4090 } 4091 4092 bool BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow) { 4093 mAtEnd = true; 4094 mPrevRow = mRow; 4095 if (aRow) { 4096 mRow = aRow; 4097 } else if (mRow) { 4098 mRow = mRow->GetNextRow(); 4099 } 4100 if (mRow) { 4101 mRowIndex = mRow->GetRowIndex(); 4102 // get to the first entry with an originating cell 4103 int32_t rgRowIndex = mRowIndex - mRowGroupStart; 4104 if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length()) ABORT1(false); 4105 const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex]; 4106 4107 for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) { 4108 CellData* cellData = row.SafeElementAt(mColIndex); 4109 if (!cellData) { // add a dead cell data 4110 TableArea damageArea; 4111 cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex, 4112 false, 0, damageArea); 4113 if (!cellData) ABORT1(false); 4114 } 4115 if (cellData && (cellData->IsOrig() || cellData->IsDead())) { 4116 break; 4117 } 4118 } 4119 mIsNewRow = true; 4120 mAtEnd = false; 4121 } else 4122 ABORT1(false); 4123 4124 return !mAtEnd; 4125 } 4126 4127 bool BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow) { 4128 mAtEnd = true; 4129 int32_t numRowGroups = mRowGroups.Length(); 4130 mCellMap = nullptr; 4131 for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) { 4132 mRowGroup = mRowGroups[mRowGroupIndex]; 4133 int32_t rowCount = mRowGroup->GetRowCount(); 4134 mRowGroupStart = mRowGroup->GetStartRowIndex(); 4135 mRowGroupEnd = mRowGroupStart + rowCount - 1; 4136 if (rowCount > 0) { 4137 mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap); 4138 if (!mCellMap) ABORT1(false); 4139 nsTableRowFrame* firstRow = mRowGroup->GetFirstRow(); 4140 if (aFindFirstDamagedRow) { 4141 if ((mAreaStart.y >= mRowGroupStart) && 4142 (mAreaStart.y <= mRowGroupEnd)) { 4143 // the damage area starts in the row group 4144 4145 // find the correct first damaged row 4146 int32_t numRows = mAreaStart.y - mRowGroupStart; 4147 for (int32_t i = 0; i < numRows; i++) { 4148 firstRow = firstRow->GetNextRow(); 4149 if (!firstRow) ABORT1(false); 4150 } 4151 4152 } else { 4153 continue; 4154 } 4155 } 4156 if (SetNewRow(firstRow)) { // sets mAtEnd 4157 break; 4158 } 4159 } 4160 } 4161 4162 return !mAtEnd; 4163 } 4164 4165 void BCMapCellIterator::First(BCMapCellInfo& aMapInfo) { 4166 aMapInfo.ResetCellInfo(); 4167 4168 SetNewRowGroup(true); // sets mAtEnd 4169 while (!mAtEnd) { 4170 if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { 4171 BCCellData* cellData = static_cast<BCCellData*>( 4172 mCellMap->GetDataAt(mAreaStart.y - mRowGroupStart, mAreaStart.x)); 4173 if (cellData && (cellData->IsOrig() || cellData->IsDead())) { 4174 aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this); 4175 return; 4176 } else { 4177 NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)), 4178 "damage area expanded incorrectly"); 4179 } 4180 } 4181 SetNewRowGroup(true); // sets mAtEnd 4182 } 4183 } 4184 4185 void BCMapCellIterator::Next(BCMapCellInfo& aMapInfo) { 4186 if (mAtEnd) ABORT0(); 4187 aMapInfo.ResetCellInfo(); 4188 4189 mIsNewRow = false; 4190 mColIndex++; 4191 while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) { 4192 for (; mColIndex <= mAreaEnd.x; mColIndex++) { 4193 int32_t rgRowIndex = mRowIndex - mRowGroupStart; 4194 BCCellData* cellData = 4195 static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, mColIndex)); 4196 if (!cellData) { // add a dead cell data 4197 TableArea damageArea; 4198 cellData = static_cast<BCCellData*>(mCellMap->AppendCell( 4199 *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea)); 4200 if (!cellData) ABORT0(); 4201 } 4202 if (cellData && (cellData->IsOrig() || cellData->IsDead())) { 4203 aMapInfo.SetInfo(mRow, mColIndex, cellData, this); 4204 return; 4205 } 4206 } 4207 if (mRowIndex >= mRowGroupEnd) { 4208 SetNewRowGroup(false); // could set mAtEnd 4209 } else { 4210 SetNewRow(); // could set mAtEnd 4211 } 4212 } 4213 mAtEnd = true; 4214 } 4215 4216 void BCMapCellIterator::PeekIEnd(const BCMapCellInfo& aRefInfo, 4217 int32_t aRowIndex, BCMapCellInfo& aAjaInfo) { 4218 PeekIAt(aRefInfo, aRowIndex, aRefInfo.mColIndex + aRefInfo.mColSpan, 4219 aAjaInfo); 4220 } 4221 4222 void BCMapCellIterator::PeekBEnd(const BCMapCellInfo& aRefInfo, 4223 int32_t aColIndex, BCMapCellInfo& aAjaInfo) { 4224 aAjaInfo.ResetCellInfo(); 4225 int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan; 4226 int32_t rgRowIndex = rowIndex - mRowGroupStart; 4227 nsTableRowGroupFrame* rg = mRowGroup; 4228 nsCellMap* cellMap = mCellMap; 4229 nsTableRowFrame* nextRow = nullptr; 4230 if (rowIndex > mRowGroupEnd) { 4231 int32_t nextRgIndex = mRowGroupIndex; 4232 do { 4233 nextRgIndex++; 4234 rg = mRowGroups.SafeElementAt(nextRgIndex); 4235 if (rg) { 4236 cellMap = mTableCellMap->GetMapFor(rg, cellMap); 4237 if (!cellMap) ABORT0(); 4238 // First row of the next row group 4239 rgRowIndex = 0; 4240 nextRow = rg->GetFirstRow(); 4241 } 4242 } while (rg && !nextRow); 4243 if (!rg) { 4244 return; 4245 } 4246 } else { 4247 // get the row within the same row group 4248 nextRow = mRow; 4249 for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) { 4250 nextRow = nextRow->GetNextRow(); 4251 if (!nextRow) ABORT0(); 4252 } 4253 } 4254 4255 BCCellData* cellData = 4256 static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex)); 4257 if (!cellData) { // add a dead cell data 4258 NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error"); 4259 TableArea damageArea; 4260 cellData = static_cast<BCCellData*>(cellMap->AppendCell( 4261 *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea)); 4262 if (!cellData) ABORT0(); 4263 } 4264 if (cellData->IsColSpan()) { 4265 aColIndex -= static_cast<int32_t>(cellData->GetColSpanOffset()); 4266 cellData = 4267 static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex)); 4268 } 4269 aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap); 4270 } 4271 4272 void BCMapCellIterator::PeekIStart(const BCMapCellInfo& aRefInfo, 4273 int32_t aRowIndex, BCMapCellInfo& aAjaInfo) { 4274 NS_ASSERTION(aRefInfo.mColIndex != 0, "program error"); 4275 PeekIAt(aRefInfo, aRowIndex, aRefInfo.mColIndex - 1, aAjaInfo); 4276 } 4277 4278 void BCMapCellIterator::PeekIAt(const BCMapCellInfo& aRefInfo, 4279 int32_t aRowIndex, int32_t aColIndex, 4280 BCMapCellInfo& aAjaInfo) { 4281 aAjaInfo.ResetCellInfo(); 4282 int32_t rgRowIndex = aRowIndex - mRowGroupStart; 4283 4284 auto* cellData = 4285 static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, aColIndex)); 4286 if (!cellData) { // add a dead cell data 4287 NS_ASSERTION(aColIndex < mTableCellMap->GetColCount(), "program error"); 4288 TableArea damageArea; 4289 cellData = static_cast<BCCellData*>(mCellMap->AppendCell( 4290 *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea)); 4291 if (!cellData) ABORT0(); 4292 } 4293 nsTableRowFrame* row = nullptr; 4294 if (cellData->IsRowSpan()) { 4295 rgRowIndex -= static_cast<int32_t>(cellData->GetRowSpanOffset()); 4296 cellData = 4297 static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, aColIndex)); 4298 if (!cellData) ABORT0(); 4299 } else { 4300 row = mRow; 4301 } 4302 aAjaInfo.SetInfo(row, aColIndex, cellData, this); 4303 } 4304 4305 #define CELL_CORNER true 4306 4307 /** return the border style, border color and optionally the width for a given 4308 * frame and side 4309 * @param aFrame - query the info for this frame 4310 * @param aTableWM - the writing-mode of the frame 4311 * @param aSide - the side of the frame 4312 * @param aStyle - the border style 4313 * @param aColor - the border color 4314 * @param aWidth - the border width 4315 */ 4316 static void GetColorAndStyle(const nsIFrame* aFrame, WritingMode aTableWM, 4317 LogicalSide aSide, StyleBorderStyle* aStyle, 4318 nscolor* aColor, nscoord* aWidth = nullptr) { 4319 MOZ_ASSERT(aFrame, "null frame"); 4320 MOZ_ASSERT(aStyle && aColor, "null argument"); 4321 4322 // initialize out arg 4323 *aColor = 0; 4324 if (aWidth) { 4325 *aWidth = 0; 4326 } 4327 4328 const nsStyleBorder* styleData = aFrame->StyleBorder(); 4329 mozilla::Side physicalSide = aTableWM.PhysicalSide(aSide); 4330 *aStyle = styleData->GetBorderStyle(physicalSide); 4331 4332 if ((StyleBorderStyle::None == *aStyle) || 4333 (StyleBorderStyle::Hidden == *aStyle)) { 4334 return; 4335 } 4336 *aColor = aFrame->Style()->GetVisitedDependentColor( 4337 nsStyleBorder::BorderColorFieldFor(physicalSide)); 4338 4339 if (aWidth) { 4340 *aWidth = styleData->GetComputedBorderWidth(physicalSide); 4341 } 4342 } 4343 4344 /** coerce the paint style as required by CSS2.1 4345 * @param aFrame - query the info for this frame 4346 * @param aTableWM - the writing mode of the frame 4347 * @param aSide - the side of the frame 4348 * @param aStyle - the border style 4349 * @param aColor - the border color 4350 */ 4351 static void GetPaintStyleInfo(const nsIFrame* aFrame, WritingMode aTableWM, 4352 LogicalSide aSide, StyleBorderStyle* aStyle, 4353 nscolor* aColor) { 4354 GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor); 4355 if (StyleBorderStyle::Inset == *aStyle) { 4356 *aStyle = StyleBorderStyle::Ridge; 4357 } else if (StyleBorderStyle::Outset == *aStyle) { 4358 *aStyle = StyleBorderStyle::Groove; 4359 } 4360 } 4361 4362 class nsDelayedCalcBCBorders : public Runnable { 4363 public: 4364 explicit nsDelayedCalcBCBorders(nsIFrame* aFrame) 4365 : mozilla::Runnable("nsDelayedCalcBCBorders"), mFrame(aFrame) {} 4366 4367 NS_IMETHOD Run() override { 4368 if (mFrame) { 4369 nsTableFrame* tableFrame = static_cast<nsTableFrame*>(mFrame.GetFrame()); 4370 if (tableFrame->NeedToCalcBCBorders()) { 4371 tableFrame->CalcBCBorders(); 4372 } 4373 } 4374 return NS_OK; 4375 } 4376 4377 private: 4378 WeakFrame mFrame; 4379 }; 4380 4381 bool nsTableFrame::BCRecalcNeeded(ComputedStyle* aOldComputedStyle, 4382 ComputedStyle* aNewComputedStyle) { 4383 // Attention: the old ComputedStyle is the one we're forgetting, 4384 // and hence possibly completely bogus for GetStyle* purposes. 4385 // We use PeekStyleData instead. 4386 4387 const nsStyleBorder* oldStyleData = aOldComputedStyle->StyleBorder(); 4388 const nsStyleBorder* newStyleData = aNewComputedStyle->StyleBorder(); 4389 nsChangeHint change = newStyleData->CalcDifference(*oldStyleData); 4390 if (!change) { 4391 return false; 4392 } 4393 if (change & nsChangeHint_NeedReflow) { 4394 return true; // the caller only needs to mark the bc damage area 4395 } 4396 if (change & nsChangeHint_RepaintFrame) { 4397 // we need to recompute the borders and the caller needs to mark 4398 // the bc damage area 4399 // XXX In principle this should only be necessary for border style changes 4400 // However the bc painting code tries to maximize the drawn border segments 4401 // so it stores in the cellmap where a new border segment starts and this 4402 // introduces a unwanted cellmap data dependence on color 4403 nsCOMPtr<nsIRunnable> evt = new nsDelayedCalcBCBorders(this); 4404 nsresult rv = GetContent()->OwnerDoc()->Dispatch(evt.forget()); 4405 return NS_SUCCEEDED(rv); 4406 } 4407 return false; 4408 } 4409 4410 // Compare two border segments, this comparison depends whether the two 4411 // segments meet at a corner and whether the second segment is inline-dir. 4412 // The return value is whichever of aBorder1 or aBorder2 dominates. 4413 static const BCCellBorder& CompareBorders( 4414 bool aIsCorner, // Pass true for corner calculations 4415 const BCCellBorder& aBorder1, const BCCellBorder& aBorder2, 4416 bool aSecondIsInlineDir, bool* aFirstDominates = nullptr) { 4417 bool firstDominates = true; 4418 4419 if (StyleBorderStyle::Hidden == aBorder1.style) { 4420 firstDominates = !aIsCorner; 4421 } else if (StyleBorderStyle::Hidden == aBorder2.style) { 4422 firstDominates = aIsCorner; 4423 } else if (aBorder1.width < aBorder2.width) { 4424 firstDominates = false; 4425 } else if (aBorder1.width == aBorder2.width) { 4426 if (static_cast<uint8_t>(aBorder1.style) < 4427 static_cast<uint8_t>(aBorder2.style)) { 4428 firstDominates = false; 4429 } else if (aBorder1.style == aBorder2.style) { 4430 if (aBorder1.owner == aBorder2.owner) { 4431 firstDominates = !aSecondIsInlineDir; 4432 } else if (aBorder1.owner < aBorder2.owner) { 4433 firstDominates = false; 4434 } 4435 } 4436 } 4437 4438 if (aFirstDominates) { 4439 *aFirstDominates = firstDominates; 4440 } 4441 4442 if (firstDominates) { 4443 return aBorder1; 4444 } 4445 return aBorder2; 4446 } 4447 4448 /** calc the dominant border by considering the table, row/col group, row/col, 4449 * cell. 4450 * Depending on whether the side is block-dir or inline-dir and whether 4451 * adjacent frames are taken into account the ownership of a single border 4452 * segment is defined. The return value is the dominating border 4453 * The cellmap stores only bstart and istart borders for each cellmap position. 4454 * If the cell border is owned by the cell that is istart-wards of the border 4455 * it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other 4456 * scenarios with a adjacent owner. 4457 * @param xxxFrame - the frame for style information, might be zero if 4458 * it should not be considered 4459 * @param aTableWM - the writing mode of the frame 4460 * @param aSide - side of the frames that should be considered 4461 * @param aAja - the border comparison takes place from the point of 4462 * a frame that is adjacent to the cellmap entry, for 4463 * when a cell owns its lower border it will be the 4464 * adjacent owner as in the cellmap only bstart and 4465 * istart borders are stored. 4466 */ 4467 static BCCellBorder CompareBorders( 4468 const nsIFrame* aTableFrame, const nsIFrame* aColGroupFrame, 4469 const nsIFrame* aColFrame, const nsIFrame* aRowGroupFrame, 4470 const nsIFrame* aRowFrame, const nsIFrame* aCellFrame, WritingMode aTableWM, 4471 LogicalSide aSide, bool aAja) { 4472 BCCellBorder border, tempBorder; 4473 bool inlineAxis = IsBlock(aSide); 4474 4475 // start with the table as dominant if present 4476 if (aTableFrame) { 4477 GetColorAndStyle(aTableFrame, aTableWM, aSide, &border.style, &border.color, 4478 &border.width); 4479 border.owner = eTableOwner; 4480 if (StyleBorderStyle::Hidden == border.style) { 4481 return border; 4482 } 4483 } 4484 // see if the colgroup is dominant 4485 if (aColGroupFrame) { 4486 GetColorAndStyle(aColGroupFrame, aTableWM, aSide, &tempBorder.style, 4487 &tempBorder.color, &tempBorder.width); 4488 tempBorder.owner = aAja && !inlineAxis ? eAjaColGroupOwner : eColGroupOwner; 4489 // pass here and below false for aSecondIsInlineDir as it is only used for 4490 // corner calculations. 4491 border = CompareBorders(!CELL_CORNER, border, tempBorder, false); 4492 if (StyleBorderStyle::Hidden == border.style) { 4493 return border; 4494 } 4495 } 4496 // see if the col is dominant 4497 if (aColFrame) { 4498 GetColorAndStyle(aColFrame, aTableWM, aSide, &tempBorder.style, 4499 &tempBorder.color, &tempBorder.width); 4500 tempBorder.owner = aAja && !inlineAxis ? eAjaColOwner : eColOwner; 4501 border = CompareBorders(!CELL_CORNER, border, tempBorder, false); 4502 if (StyleBorderStyle::Hidden == border.style) { 4503 return border; 4504 } 4505 } 4506 // see if the rowgroup is dominant 4507 if (aRowGroupFrame) { 4508 GetColorAndStyle(aRowGroupFrame, aTableWM, aSide, &tempBorder.style, 4509 &tempBorder.color, &tempBorder.width); 4510 tempBorder.owner = aAja && inlineAxis ? eAjaRowGroupOwner : eRowGroupOwner; 4511 border = CompareBorders(!CELL_CORNER, border, tempBorder, false); 4512 if (StyleBorderStyle::Hidden == border.style) { 4513 return border; 4514 } 4515 } 4516 // see if the row is dominant 4517 if (aRowFrame) { 4518 GetColorAndStyle(aRowFrame, aTableWM, aSide, &tempBorder.style, 4519 &tempBorder.color, &tempBorder.width); 4520 tempBorder.owner = aAja && inlineAxis ? eAjaRowOwner : eRowOwner; 4521 border = CompareBorders(!CELL_CORNER, border, tempBorder, false); 4522 if (StyleBorderStyle::Hidden == border.style) { 4523 return border; 4524 } 4525 } 4526 // see if the cell is dominant 4527 if (aCellFrame) { 4528 GetColorAndStyle(aCellFrame, aTableWM, aSide, &tempBorder.style, 4529 &tempBorder.color, &tempBorder.width); 4530 tempBorder.owner = aAja ? eAjaCellOwner : eCellOwner; 4531 border = CompareBorders(!CELL_CORNER, border, tempBorder, false); 4532 } 4533 return border; 4534 } 4535 4536 static bool Perpendicular(mozilla::LogicalSide aSide1, 4537 mozilla::LogicalSide aSide2) { 4538 return IsInline(aSide1) != IsInline(aSide2); 4539 } 4540 4541 // Initial value indicating that BCCornerInfo's ownerStyle hasn't been set yet. 4542 #define BORDER_STYLE_UNSET static_cast<StyleBorderStyle>(255) 4543 4544 struct BCCornerInfo { 4545 BCCornerInfo() { 4546 ownerColor = 0; 4547 ownerWidth = subWidth = ownerElem = subSide = subElem = hasDashDot = 4548 numSegs = bevel = 0; 4549 ownerSide = static_cast<uint16_t>(LogicalSide::BStart); 4550 ownerStyle = BORDER_STYLE_UNSET; 4551 subStyle = StyleBorderStyle::Solid; 4552 } 4553 4554 void Set(mozilla::LogicalSide aSide, BCCellBorder border); 4555 4556 void Update(mozilla::LogicalSide aSide, BCCellBorder border); 4557 4558 nscolor ownerColor; // color of borderOwner 4559 uint16_t ownerWidth; // width of borderOwner 4560 uint16_t subWidth; // width of the largest border intersecting the 4561 // border perpendicular to ownerSide 4562 StyleBorderStyle subStyle; // border style of subElem 4563 StyleBorderStyle ownerStyle; // border style of ownerElem 4564 uint16_t ownerSide : 2; // LogicalSide (e.g LogicalSide::BStart, etc) of the 4565 // border owning the corner relative to the corner 4566 uint16_t 4567 ownerElem : 4; // elem type (e.g. eTable, eGroup, etc) owning the corner 4568 uint16_t subSide : 2; // side of border with subWidth relative to the corner 4569 uint16_t subElem : 4; // elem type (e.g. eTable, eGroup, etc) of sub owner 4570 uint16_t hasDashDot : 1; // does a dashed, dotted segment enter the corner, 4571 // they cannot be beveled 4572 uint16_t numSegs : 3; // number of segments entering corner 4573 uint16_t bevel : 1; // is the corner beveled (uses the above two fields 4574 // together with subWidth) 4575 // 7 bits are unused 4576 }; 4577 4578 // Start a new border at this corner, going in the direction of a given side. 4579 void BCCornerInfo::Set(mozilla::LogicalSide aSide, BCCellBorder aBorder) { 4580 // FIXME bug 1508921: We mask 4-bit BCBorderOwner enum to 3 bits to preserve 4581 // buggy behavior found by the frame_above_rules_all.html mochitest. 4582 ownerElem = aBorder.owner & 0x7; 4583 4584 ownerStyle = aBorder.style; 4585 ownerWidth = aBorder.width; 4586 ownerColor = aBorder.color; 4587 ownerSide = static_cast<uint16_t>(aSide); 4588 hasDashDot = 0; 4589 numSegs = 0; 4590 if (aBorder.width > 0) { 4591 numSegs++; 4592 hasDashDot = (StyleBorderStyle::Dashed == aBorder.style) || 4593 (StyleBorderStyle::Dotted == aBorder.style); 4594 } 4595 bevel = 0; 4596 subWidth = 0; 4597 // the following will get set later 4598 subSide = static_cast<uint16_t>(IsInline(aSide) ? LogicalSide::BStart 4599 : LogicalSide::IStart); 4600 subElem = eTableOwner; 4601 subStyle = StyleBorderStyle::Solid; 4602 } 4603 4604 // Add a new border going in the direction of a given side, and update the 4605 // dominant border. 4606 void BCCornerInfo::Update(mozilla::LogicalSide aSide, BCCellBorder aBorder) { 4607 if (ownerStyle == BORDER_STYLE_UNSET) { 4608 Set(aSide, aBorder); 4609 } else { 4610 bool isInline = IsInline(aSide); // relative to the corner 4611 BCCellBorder oldBorder, tempBorder; 4612 oldBorder.owner = (BCBorderOwner)ownerElem; 4613 oldBorder.style = ownerStyle; 4614 oldBorder.width = ownerWidth; 4615 oldBorder.color = ownerColor; 4616 4617 LogicalSide oldSide = LogicalSide(ownerSide); 4618 4619 bool existingWins = false; 4620 tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline, 4621 &existingWins); 4622 4623 ownerElem = tempBorder.owner; 4624 ownerStyle = tempBorder.style; 4625 ownerWidth = tempBorder.width; 4626 ownerColor = tempBorder.color; 4627 if (existingWins) { // existing corner is dominant 4628 if (::Perpendicular(LogicalSide(ownerSide), aSide)) { 4629 // see if the new sub info replaces the old 4630 BCCellBorder subBorder; 4631 subBorder.owner = (BCBorderOwner)subElem; 4632 subBorder.style = subStyle; 4633 subBorder.width = subWidth; 4634 subBorder.color = 0; // we are not interested in subBorder color 4635 bool firstWins; 4636 4637 tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline, 4638 &firstWins); 4639 4640 subElem = tempBorder.owner; 4641 subStyle = tempBorder.style; 4642 subWidth = tempBorder.width; 4643 if (!firstWins) { 4644 subSide = static_cast<uint16_t>(aSide); 4645 } 4646 } 4647 } else { // input args are dominant 4648 ownerSide = static_cast<uint16_t>(aSide); 4649 if (::Perpendicular(oldSide, LogicalSide(ownerSide))) { 4650 subElem = oldBorder.owner; 4651 subStyle = oldBorder.style; 4652 subWidth = oldBorder.width; 4653 subSide = static_cast<uint16_t>(oldSide); 4654 } 4655 } 4656 if (aBorder.width > 0) { 4657 numSegs++; 4658 if (!hasDashDot && ((StyleBorderStyle::Dashed == aBorder.style) || 4659 (StyleBorderStyle::Dotted == aBorder.style))) { 4660 hasDashDot = 1; 4661 } 4662 } 4663 4664 // bevel the corner if only two perpendicular non dashed/dotted segments 4665 // enter the corner 4666 bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot); 4667 } 4668 } 4669 4670 struct BCCorners { 4671 BCCorners(int32_t aNumCorners, int32_t aStartIndex); 4672 4673 BCCornerInfo& operator[](int32_t i) const { 4674 NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error"); 4675 return corners[std::clamp(i, startIndex, endIndex) - startIndex]; 4676 } 4677 4678 int32_t startIndex; 4679 int32_t endIndex; 4680 UniquePtr<BCCornerInfo[]> corners; 4681 }; 4682 4683 BCCorners::BCCorners(int32_t aNumCorners, int32_t aStartIndex) { 4684 NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error"); 4685 startIndex = aStartIndex; 4686 endIndex = aStartIndex + aNumCorners - 1; 4687 corners = MakeUnique<BCCornerInfo[]>(aNumCorners); 4688 } 4689 4690 struct BCCellBorders { 4691 BCCellBorders(int32_t aNumBorders, int32_t aStartIndex); 4692 4693 BCCellBorder& operator[](int32_t i) const { 4694 NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error"); 4695 return borders[std::clamp(i, startIndex, endIndex) - startIndex]; 4696 } 4697 4698 int32_t startIndex; 4699 int32_t endIndex; 4700 UniquePtr<BCCellBorder[]> borders; 4701 }; 4702 4703 BCCellBorders::BCCellBorders(int32_t aNumBorders, int32_t aStartIndex) { 4704 NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error"); 4705 startIndex = aStartIndex; 4706 endIndex = aStartIndex + aNumBorders - 1; 4707 borders = MakeUnique<BCCellBorder[]>(aNumBorders); 4708 } 4709 4710 // this function sets the new border properties and returns true if the border 4711 // segment will start a new segment and not be accumulated into the previous 4712 // segment. 4713 static bool SetBorder(const BCCellBorder& aNewBorder, BCCellBorder& aBorder) { 4714 bool changed = (aNewBorder.style != aBorder.style) || 4715 (aNewBorder.width != aBorder.width) || 4716 (aNewBorder.color != aBorder.color); 4717 aBorder.color = aNewBorder.color; 4718 aBorder.width = aNewBorder.width; 4719 aBorder.style = aNewBorder.style; 4720 aBorder.owner = aNewBorder.owner; 4721 4722 return changed; 4723 } 4724 4725 // this function will set the inline-dir border. It will return true if the 4726 // existing segment will not be continued. Having a block-dir owner of a corner 4727 // should also start a new segment. 4728 static bool SetInlineDirBorder(const BCCellBorder& aNewBorder, 4729 const BCCornerInfo& aCorner, 4730 BCCellBorder& aBorder) { 4731 bool startSeg = ::SetBorder(aNewBorder, aBorder); 4732 if (!startSeg) { 4733 startSeg = !IsInline(LogicalSide(aCorner.ownerSide)); 4734 } 4735 return startSeg; 4736 } 4737 4738 // Make the damage area larger on the top and bottom by at least one row and on 4739 // the left and right at least one column. This is done so that adjacent 4740 // elements are part of the border calculations. The extra segments and borders 4741 // outside the actual damage area will not be updated in the cell map, because 4742 // they in turn would need info from adjacent segments outside the damage area 4743 // to be accurate. 4744 void nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const { 4745 int32_t numRows = GetRowCount(); 4746 int32_t numCols = GetColCount(); 4747 4748 int32_t firstColIdx = aArea.StartCol(); 4749 int32_t lastColIdx = aArea.EndCol() - 1; 4750 int32_t startRowIdx = aArea.StartRow(); 4751 int32_t endRowIdx = aArea.EndRow() - 1; 4752 4753 // expand the damage area in each direction 4754 if (firstColIdx > 0) { 4755 firstColIdx--; 4756 } 4757 if (lastColIdx < (numCols - 1)) { 4758 lastColIdx++; 4759 } 4760 if (startRowIdx > 0) { 4761 startRowIdx--; 4762 } 4763 if (endRowIdx < (numRows - 1)) { 4764 endRowIdx++; 4765 } 4766 // Check the damage area so that there are no cells spanning in or out. If 4767 // there are any then make the damage area as big as the table, similarly to 4768 // the way the cell map decides whether to rebuild versus expand. This could 4769 // be optimized to expand to the smallest area that contains no spanners, but 4770 // it may not be worth the effort in general, and it would need to be done in 4771 // the cell map as well. 4772 bool haveSpanner = false; 4773 if ((firstColIdx > 0) || (lastColIdx < (numCols - 1)) || (startRowIdx > 0) || 4774 (endRowIdx < (numRows - 1))) { 4775 nsTableCellMap* tableCellMap = GetCellMap(); 4776 if (!tableCellMap) ABORT0(); 4777 // Get the ordered row groups 4778 RowGroupArray rowGroups = OrderedRowGroups(); 4779 4780 // Scope outside loop to be used as hint. 4781 nsCellMap* cellMap = nullptr; 4782 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { 4783 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; 4784 int32_t rgStartY = rgFrame->GetStartRowIndex(); 4785 int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1; 4786 if (endRowIdx < rgStartY) { 4787 break; 4788 } 4789 cellMap = tableCellMap->GetMapFor(rgFrame, cellMap); 4790 if (!cellMap) ABORT0(); 4791 // check for spanners from above and below 4792 if ((startRowIdx > 0) && (startRowIdx >= rgStartY) && 4793 (startRowIdx <= rgEndY)) { 4794 if (uint32_t(startRowIdx - rgStartY) >= cellMap->mRows.Length()) 4795 ABORT0(); 4796 const nsCellMap::CellDataArray& row = 4797 cellMap->mRows[startRowIdx - rgStartY]; 4798 for (int32_t x = firstColIdx; x <= lastColIdx; x++) { 4799 CellData* cellData = row.SafeElementAt(x); 4800 if (cellData && (cellData->IsRowSpan())) { 4801 haveSpanner = true; 4802 break; 4803 } 4804 } 4805 if (endRowIdx < rgEndY) { 4806 if (uint32_t(endRowIdx + 1 - rgStartY) >= cellMap->mRows.Length()) 4807 ABORT0(); 4808 const nsCellMap::CellDataArray& row2 = 4809 cellMap->mRows[endRowIdx + 1 - rgStartY]; 4810 for (int32_t x = firstColIdx; x <= lastColIdx; x++) { 4811 CellData* cellData = row2.SafeElementAt(x); 4812 if (cellData && (cellData->IsRowSpan())) { 4813 haveSpanner = true; 4814 break; 4815 } 4816 } 4817 } 4818 } 4819 // check for spanners on the left and right 4820 int32_t iterStartY; 4821 int32_t iterEndY; 4822 if ((startRowIdx >= rgStartY) && (startRowIdx <= rgEndY)) { 4823 // the damage area starts in the row group 4824 iterStartY = startRowIdx; 4825 iterEndY = std::min(endRowIdx, rgEndY); 4826 } else if ((endRowIdx >= rgStartY) && (endRowIdx <= rgEndY)) { 4827 // the damage area ends in the row group 4828 iterStartY = rgStartY; 4829 iterEndY = endRowIdx; 4830 } else if ((rgStartY >= startRowIdx) && (rgEndY <= endRowIdx)) { 4831 // the damage area contains the row group 4832 iterStartY = rgStartY; 4833 iterEndY = rgEndY; 4834 } else { 4835 // the damage area does not overlap the row group 4836 continue; 4837 } 4838 NS_ASSERTION(iterStartY >= 0 && iterEndY >= 0, 4839 "table index values are expected to be nonnegative"); 4840 for (int32_t y = iterStartY; y <= iterEndY; y++) { 4841 if (uint32_t(y - rgStartY) >= cellMap->mRows.Length()) ABORT0(); 4842 const nsCellMap::CellDataArray& row = cellMap->mRows[y - rgStartY]; 4843 CellData* cellData = row.SafeElementAt(firstColIdx); 4844 if (cellData && (cellData->IsColSpan())) { 4845 haveSpanner = true; 4846 break; 4847 } 4848 if (lastColIdx < (numCols - 1)) { 4849 cellData = row.SafeElementAt(lastColIdx + 1); 4850 if (cellData && (cellData->IsColSpan())) { 4851 haveSpanner = true; 4852 break; 4853 } 4854 } 4855 } 4856 } 4857 } 4858 4859 // If the damage area includes the edge of the table, we have to expand 4860 // the damage area across that whole edge. This is because table-edge 4861 // borders take the maximum border width among all cells on that edge. 4862 // i.e. If the first row is damaged, then we consider all the cols to 4863 // be damaged, and vice versa. 4864 if (haveSpanner || startRowIdx == 0 || endRowIdx == numRows - 1) { 4865 aArea.StartCol() = 0; 4866 aArea.ColCount() = numCols; 4867 } else { 4868 aArea.StartCol() = firstColIdx; 4869 aArea.ColCount() = 1 + lastColIdx - firstColIdx; 4870 } 4871 4872 if (haveSpanner || firstColIdx == 0 || lastColIdx == numCols - 1) { 4873 aArea.StartRow() = 0; 4874 aArea.RowCount() = numRows; 4875 } else { 4876 aArea.StartRow() = startRowIdx; 4877 aArea.RowCount() = 1 + endRowIdx - startRowIdx; 4878 } 4879 } 4880 4881 #define ADJACENT true 4882 #define INLINE_DIR true 4883 4884 void BCMapTableInfo::SetTableIStartBorderWidth(nscoord aWidth) { 4885 mTableBCData->mIStartBorderWidth = 4886 std::max(mTableBCData->mIStartBorderWidth, aWidth); 4887 } 4888 4889 void BCMapTableInfo::SetTableIEndBorderWidth(nscoord aWidth) { 4890 mTableBCData->mIEndBorderWidth = 4891 std::max(mTableBCData->mIEndBorderWidth, aWidth); 4892 } 4893 4894 void BCMapTableInfo::SetTableBStartBorderWidth(nscoord aWidth) { 4895 mTableBCData->mBStartBorderWidth = 4896 std::max(mTableBCData->mBStartBorderWidth, aWidth); 4897 } 4898 4899 void BCMapTableInfo::SetTableBEndBorderWidth(nscoord aWidth) { 4900 mTableBCData->mBEndBorderWidth = 4901 std::max(mTableBCData->mBEndBorderWidth, aWidth); 4902 } 4903 4904 void BCMapCellInfo::ResetIStartBorderWidths() { 4905 if (mCell) { 4906 mCell->SetBorderWidth(LogicalSide::IStart, 0); 4907 } 4908 if (mStartCol) { 4909 mStartCol->SetIStartBorderWidth(0); 4910 } 4911 } 4912 4913 void BCMapCellInfo::ResetIEndBorderWidths() { 4914 if (mCell) { 4915 mCell->SetBorderWidth(LogicalSide::IEnd, 0); 4916 } 4917 if (mEndCol) { 4918 mEndCol->SetIEndBorderWidth(0); 4919 } 4920 } 4921 4922 void BCMapCellInfo::ResetBStartBorderWidths() { 4923 if (mCell) { 4924 mCell->SetBorderWidth(LogicalSide::BStart, 0); 4925 } 4926 if (mStartRow) { 4927 mStartRow->SetBStartBCBorderWidth(0); 4928 } 4929 } 4930 4931 void BCMapCellInfo::ResetBEndBorderWidths() { 4932 if (mCell) { 4933 mCell->SetBorderWidth(LogicalSide::BEnd, 0); 4934 } 4935 if (mEndRow) { 4936 mEndRow->SetBEndBCBorderWidth(0); 4937 } 4938 } 4939 4940 void BCMapCellInfo::SetIStartBorderWidths(nscoord aWidth) { 4941 if (mCell) { 4942 mCell->SetBorderWidth( 4943 LogicalSide::IStart, 4944 std::max(aWidth, mCell->GetBorderWidth(LogicalSide::IStart))); 4945 } 4946 if (mStartCol) { 4947 nscoord half = BC_BORDER_END_HALF(aWidth); 4948 mStartCol->SetIStartBorderWidth( 4949 std::max(half, mStartCol->GetIStartBorderWidth())); 4950 } 4951 } 4952 4953 void BCMapCellInfo::SetIEndBorderWidths(nscoord aWidth) { 4954 // update the borders of the cells and cols affected 4955 if (mCell) { 4956 mCell->SetBorderWidth( 4957 LogicalSide::IEnd, 4958 std::max(aWidth, mCell->GetBorderWidth(LogicalSide::IEnd))); 4959 } 4960 if (mEndCol) { 4961 nscoord half = BC_BORDER_START_HALF(aWidth); 4962 mEndCol->SetIEndBorderWidth(std::max(half, mEndCol->GetIEndBorderWidth())); 4963 } 4964 } 4965 4966 void BCMapCellInfo::SetBStartBorderWidths(nscoord aWidth) { 4967 if (mCell) { 4968 mCell->SetBorderWidth( 4969 LogicalSide::BStart, 4970 std::max(aWidth, mCell->GetBorderWidth(LogicalSide::BStart))); 4971 } 4972 if (mStartRow) { 4973 nscoord half = BC_BORDER_END_HALF(aWidth); 4974 mStartRow->SetBStartBCBorderWidth( 4975 std::max(half, mStartRow->GetBStartBCBorderWidth())); 4976 } 4977 } 4978 4979 void BCMapCellInfo::SetBEndBorderWidths(nscoord aWidth) { 4980 // update the borders of the affected cells and rows 4981 if (mCell) { 4982 mCell->SetBorderWidth( 4983 LogicalSide::BEnd, 4984 std::max(aWidth, mCell->GetBorderWidth(LogicalSide::BEnd))); 4985 } 4986 if (mEndRow) { 4987 nscoord half = BC_BORDER_START_HALF(aWidth); 4988 mEndRow->SetBEndBCBorderWidth( 4989 std::max(half, mEndRow->GetBEndBCBorderWidth())); 4990 } 4991 } 4992 4993 void BCMapCellInfo::SetColumn(int32_t aColX) { 4994 mCurrentColFrame = mTableFirstInFlow->GetColFrame(aColX); 4995 mCurrentColGroupFrame = 4996 static_cast<nsTableColGroupFrame*>(mCurrentColFrame->GetParent()); 4997 if (!mCurrentColGroupFrame) { 4998 NS_ERROR("null mCurrentColGroupFrame"); 4999 } 5000 } 5001 5002 void BCMapCellInfo::IncrementRow(bool aResetToBStartRowOfCell) { 5003 mCurrentRowFrame = 5004 aResetToBStartRowOfCell ? mStartRow : mCurrentRowFrame->GetNextRow(); 5005 } 5006 5007 BCCellBorder BCMapCellInfo::GetBStartEdgeBorder() { 5008 return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, 5009 mRowGroup, mStartRow, mCell, mTableWM, 5010 LogicalSide::BStart, !ADJACENT); 5011 } 5012 5013 BCCellBorder BCMapCellInfo::GetBEndEdgeBorder() { 5014 return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, 5015 mRowGroup, mEndRow, mCell, mTableWM, LogicalSide::BEnd, 5016 ADJACENT); 5017 } 5018 BCCellBorder BCMapCellInfo::GetIStartEdgeBorder() { 5019 return CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup, 5020 mCurrentRowFrame, mCell, mTableWM, LogicalSide::IStart, 5021 !ADJACENT); 5022 } 5023 BCCellBorder BCMapCellInfo::GetIEndEdgeBorder() { 5024 return CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup, 5025 mCurrentRowFrame, mCell, mTableWM, LogicalSide::IEnd, 5026 ADJACENT); 5027 } 5028 BCCellBorder BCMapCellInfo::GetIEndInternalBorder() { 5029 const nsIFrame* cg = mCgAtEnd ? mColGroup : nullptr; 5030 return CompareBorders(nullptr, cg, mEndCol, nullptr, nullptr, mCell, mTableWM, 5031 LogicalSide::IEnd, ADJACENT); 5032 } 5033 5034 BCCellBorder BCMapCellInfo::GetIStartInternalBorder() { 5035 const nsIFrame* cg = mCgAtStart ? mColGroup : nullptr; 5036 return CompareBorders(nullptr, cg, mStartCol, nullptr, nullptr, mCell, 5037 mTableWM, LogicalSide::IStart, !ADJACENT); 5038 } 5039 5040 BCCellBorder BCMapCellInfo::GetBEndInternalBorder() { 5041 const nsIFrame* rg = mRgAtEnd ? mRowGroup : nullptr; 5042 return CompareBorders(nullptr, nullptr, nullptr, rg, mEndRow, mCell, mTableWM, 5043 LogicalSide::BEnd, ADJACENT); 5044 } 5045 5046 BCCellBorder BCMapCellInfo::GetBStartInternalBorder() { 5047 const nsIFrame* rg = mRgAtStart ? mRowGroup : nullptr; 5048 return CompareBorders(nullptr, nullptr, nullptr, rg, mStartRow, mCell, 5049 mTableWM, LogicalSide::BStart, !ADJACENT); 5050 } 5051 5052 // Calculate border information for border-collapsed tables. 5053 // Because borders of table/row/cell, etc merge into one, we need to 5054 // determine which border dominates at each cell. In addition, corner-specific 5055 // information, e.g. bevelling, is computed as well. 5056 // 5057 // Here is the order for storing border edges in the cell map as a cell is 5058 // processed. 5059 // 5060 // For each cell, at least 4 edges are processed: 5061 // * There are colspan * N block-start and block-end edges. 5062 // * There are rowspan * N inline-start and inline-end edges. 5063 // 5064 // 1) If the cell being processed is at the block-start of the table, store the 5065 // block-start edge. 5066 // 2) If the cell being processed is at the inline-start of the table, store 5067 // the 5068 // inline-start edge. 5069 // 3) Store the inline-end edge. 5070 // 4) Store the block-end edge. 5071 // 5072 // These steps are traced by calls to `SetBCBorderEdge`. 5073 // 5074 // Corners are indexed by columns only, to avoid allocating a full row * col 5075 // array of `BCCornerInfo`. This trades off memory allocation versus moving 5076 // previous corner information around. 5077 // 5078 // For each cell: 5079 // 1) If the cell is at the block-start of the table, but not at the 5080 // inline-start of the table, store its block-start inline-start corner. 5081 // 5082 // 2) If the cell is at the inline-start of the table, store the block-start 5083 // inline-start corner. 5084 // 5085 // 3) If the cell is at the block-start inline-end of the table, or not at the 5086 // block-start of the table, store the block-start inline-end corner. 5087 // 5088 // 4) If the cell is at the block-end inline-end of the table, store the 5089 // block-end inline-end corner. 5090 // 5091 // 5) If the cell is at the block-end of the table, store the block-end 5092 // inline-start. 5093 // 5094 // Visually, it looks like this: 5095 // 5096 // 2--1--1--1--1--1--3 5097 // | | | | | | | 5098 // 2--3--3--3--3--3--3 5099 // | | | | | | | 5100 // 2--3--3--3--3--3--3 5101 // | | | | | | | 5102 // 5--5--5--5--5--5--4 5103 // 5104 // For rowspan/colspan cells, the latest border information is propagated 5105 // along its "corners". 5106 // 5107 // These steps are traced by calls to `SetBCBorderCorner`. 5108 void nsTableFrame::CalcBCBorders() { 5109 NS_ASSERTION(IsBorderCollapse(), 5110 "calling CalcBCBorders on separated-border table"); 5111 nsTableCellMap* tableCellMap = GetCellMap(); 5112 if (!tableCellMap) ABORT0(); 5113 int32_t numRows = GetRowCount(); 5114 int32_t numCols = GetColCount(); 5115 if (!numRows || !numCols) { 5116 return; // nothing to do 5117 } 5118 5119 // Get the property holding the table damage area and border widths 5120 TableBCData* propData = GetTableBCData(); 5121 if (!propData) ABORT0(); 5122 5123 TableArea damageArea(propData->mDamageArea); 5124 // See documentation for why we do this. 5125 ExpandBCDamageArea(damageArea); 5126 5127 // We accumulate border widths as we process the cells, so we need 5128 // to reset it once in the beginning. 5129 bool tableBorderReset[4]; 5130 for (uint32_t sideX = 0; sideX < std::size(tableBorderReset); sideX++) { 5131 tableBorderReset[sideX] = false; 5132 } 5133 5134 // Storage for block-direction borders from the previous row, indexed by 5135 // columns. 5136 BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1, 5137 damageArea.StartCol()); 5138 if (!lastBlockDirBorders.borders) ABORT0(); 5139 if (damageArea.StartRow() != 0) { 5140 // Ok, we've filled with information about the previous row's borders with 5141 // the default state, which is "no borders." This is incorrect, and leaving 5142 // it will result in an erroneous behaviour if the previous row did have 5143 // borders, and the dirty rows don't, as we will not mark the beginning of 5144 // the no border segment. 5145 TableArea prevRowArea(damageArea.StartCol(), damageArea.StartRow() - 1, 5146 damageArea.ColCount(), 1); 5147 BCMapCellIterator iter(this, prevRowArea); 5148 BCMapCellInfo info(this); 5149 for (iter.First(info); !iter.mAtEnd; iter.Next(info)) { 5150 if (info.mColIndex == prevRowArea.StartCol()) { 5151 lastBlockDirBorders.borders[0] = info.GetIStartEdgeBorder(); 5152 } 5153 lastBlockDirBorders.borders[info.mColIndex - prevRowArea.StartCol() + 1] = 5154 info.GetIEndEdgeBorder(); 5155 } 5156 } 5157 // Inline direction border at block start of the table, computed by the 5158 // previous cell. Unused afterwards. 5159 Maybe<BCCellBorder> firstRowBStartEdgeBorder; 5160 BCCellBorder lastBEndBorder; 5161 // Storage for inline-direction borders from previous cells, indexed by 5162 // columns. 5163 // TODO(dshin): Why ColCount + 1? Number of inline segments should match 5164 // column count exactly, unlike block direction segments... 5165 BCCellBorders lastBEndBorders(damageArea.ColCount() + 1, 5166 damageArea.StartCol()); 5167 if (!lastBEndBorders.borders) ABORT0(); 5168 5169 BCMapCellInfo info(this); 5170 // TODO(dshin): This is basically propData, except it uses first-in-flow's 5171 // data. Consult the definition of `TableBCDataProperty` regarding 5172 // using the first-in-flow only. 5173 BCMapTableInfo tableInfo(this); 5174 5175 // Block-start corners of the cell being traversed, indexed by columns. 5176 BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol()); 5177 if (!bStartCorners.corners) ABORT0(); 5178 // Block-end corners of the cell being traversed, indexed by columns. 5179 // Note that when a new row starts, they become block-start corners and used 5180 // as such, until cleared with `Set`. 5181 BCCorners bEndCorners(damageArea.ColCount() + 1, damageArea.StartCol()); 5182 if (!bEndCorners.corners) ABORT0(); 5183 5184 BCMapCellIterator iter(this, damageArea); 5185 for (iter.First(info); !iter.mAtEnd; iter.Next(info)) { 5186 // see if firstRowBStartEdgeBorder, lastBEndBorder need to be reset 5187 if (iter.IsNewRow()) { 5188 if (info.mRowIndex == 0) { 5189 BCCellBorder border; 5190 if (info.mColIndex == 0) { 5191 border.Reset(info.mRowIndex, info.mRowSpan); 5192 } else { 5193 // Similar to lastBlockDirBorders, the previous block-start border 5194 // is filled by actually quering the adjacent cell. 5195 BCMapCellInfo ajaInfo(this); 5196 iter.PeekIStart(info, info.mRowIndex, ajaInfo); 5197 border = ajaInfo.GetBStartEdgeBorder(); 5198 } 5199 firstRowBStartEdgeBorder = Some(border); 5200 } else { 5201 firstRowBStartEdgeBorder = Nothing{}; 5202 } 5203 if (info.mColIndex == 0) { 5204 lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); 5205 } else { 5206 // Same as above, but for block-end border. 5207 BCMapCellInfo ajaInfo(this); 5208 iter.PeekIStart(info, info.mRowIndex, ajaInfo); 5209 lastBEndBorder = ajaInfo.GetBEndEdgeBorder(); 5210 } 5211 } else if (info.mColIndex > damageArea.StartCol()) { 5212 lastBEndBorder = lastBEndBorders[info.mColIndex - 1]; 5213 if (lastBEndBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) { 5214 // the bEnd border's iStart edge butts against the middle of a rowspan 5215 lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); 5216 } 5217 } 5218 5219 // find the dominant border considering the cell's bStart border and the 5220 // table, row group, row if the border is at the bStart of the table, 5221 // otherwise it was processed in a previous row 5222 if (0 == info.mRowIndex) { 5223 uint8_t idxBStart = static_cast<uint8_t>(LogicalSide::BStart); 5224 if (!tableBorderReset[idxBStart]) { 5225 tableInfo.ResetTableBStartBorderWidth(); 5226 tableBorderReset[idxBStart] = true; 5227 } 5228 bool reset = false; 5229 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex(); 5230 colIdx++) { 5231 info.SetColumn(colIdx); 5232 BCCellBorder currentBorder = info.GetBStartEdgeBorder(); 5233 BCCornerInfo& bStartIStartCorner = bStartCorners[colIdx]; 5234 // Mark inline-end direction border from this corner. 5235 if (0 == colIdx) { 5236 bStartIStartCorner.Set(LogicalSide::IEnd, currentBorder); 5237 } else { 5238 bStartIStartCorner.Update(LogicalSide::IEnd, currentBorder); 5239 tableCellMap->SetBCBorderCorner( 5240 LogicalCorner::BStartIStart, *iter.mCellMap, 0, 0, colIdx, 5241 LogicalSide(bStartIStartCorner.ownerSide), 5242 bStartIStartCorner.subWidth, bStartIStartCorner.bevel); 5243 } 5244 // Above, we set the corner `colIndex` column as having a border towards 5245 // inline-end, heading towards the next column. Vice versa is also true, 5246 // where the next column has a border heading towards this column. 5247 bStartCorners[colIdx + 1].Set(LogicalSide::IStart, currentBorder); 5248 MOZ_ASSERT(firstRowBStartEdgeBorder, 5249 "Inline start border tracking not set?"); 5250 // update firstRowBStartEdgeBorder and see if a new segment starts 5251 bool startSeg = 5252 firstRowBStartEdgeBorder 5253 ? SetInlineDirBorder(currentBorder, bStartIStartCorner, 5254 firstRowBStartEdgeBorder.ref()) 5255 : true; 5256 // store the border segment in the cell map 5257 tableCellMap->SetBCBorderEdge(LogicalSide::BStart, *iter.mCellMap, 0, 0, 5258 colIdx, 1, currentBorder.owner, 5259 currentBorder.width, startSeg); 5260 5261 // Set border width at block-start (table-wide and for the cell), but 5262 // only if it's the largest we've encountered. 5263 tableInfo.SetTableBStartBorderWidth(currentBorder.width); 5264 if (!reset) { 5265 info.ResetBStartBorderWidths(); 5266 reset = true; 5267 } 5268 info.SetBStartBorderWidths(currentBorder.width); 5269 } 5270 } else { 5271 // see if the bStart border needs to be the start of a segment due to a 5272 // block-dir border owning the corner 5273 if (info.mColIndex > 0) { 5274 BCData& data = info.mCellData->mData; 5275 if (!data.IsBStartStart()) { 5276 LogicalSide cornerSide; 5277 bool bevel; 5278 data.GetCorner(cornerSide, bevel); 5279 if (IsBlock(cornerSide)) { 5280 data.SetBStartStart(true); 5281 } 5282 } 5283 } 5284 } 5285 5286 // find the dominant border considering the cell's iStart border and the 5287 // table, col group, col if the border is at the iStart of the table, 5288 // otherwise it was processed in a previous col 5289 if (0 == info.mColIndex) { 5290 uint8_t idxIStart = static_cast<uint8_t>(LogicalSide::IStart); 5291 if (!tableBorderReset[idxIStart]) { 5292 tableInfo.ResetTableIStartBorderWidth(); 5293 tableBorderReset[idxIStart] = true; 5294 } 5295 info.mCurrentRowFrame = nullptr; 5296 bool reset = false; 5297 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); 5298 rowB++) { 5299 info.IncrementRow(rowB == info.mRowIndex); 5300 BCCellBorder currentBorder = info.GetIStartEdgeBorder(); 5301 BCCornerInfo& bStartIStartCorner = 5302 (0 == rowB) ? bStartCorners[0] : bEndCorners[0]; 5303 bStartIStartCorner.Update(LogicalSide::BEnd, currentBorder); 5304 tableCellMap->SetBCBorderCorner( 5305 LogicalCorner::BStartIStart, *iter.mCellMap, iter.mRowGroupStart, 5306 rowB, 0, LogicalSide(bStartIStartCorner.ownerSide), 5307 bStartIStartCorner.subWidth, bStartIStartCorner.bevel); 5308 bEndCorners[0].Set(LogicalSide::BStart, currentBorder); 5309 5310 // update lastBlockDirBorders and see if a new segment starts 5311 bool startSeg = SetBorder(currentBorder, lastBlockDirBorders[0]); 5312 // store the border segment in the cell map 5313 tableCellMap->SetBCBorderEdge(LogicalSide::IStart, *iter.mCellMap, 5314 iter.mRowGroupStart, rowB, info.mColIndex, 5315 1, currentBorder.owner, 5316 currentBorder.width, startSeg); 5317 // Set border width at inline-start (table-wide and for the cell), but 5318 // only if it's the largest we've encountered. 5319 tableInfo.SetTableIStartBorderWidth(currentBorder.width); 5320 if (!reset) { 5321 info.ResetIStartBorderWidths(); 5322 reset = true; 5323 } 5324 info.SetIStartBorderWidths(currentBorder.width); 5325 } 5326 } 5327 5328 // find the dominant border considering the cell's iEnd border, adjacent 5329 // cells and the table, row group, row 5330 if (info.mNumTableCols == info.GetCellEndColIndex() + 1) { 5331 // touches iEnd edge of table 5332 uint8_t idxIEnd = static_cast<uint8_t>(LogicalSide::IEnd); 5333 if (!tableBorderReset[idxIEnd]) { 5334 tableInfo.ResetTableIEndBorderWidth(); 5335 tableBorderReset[idxIEnd] = true; 5336 } 5337 info.mCurrentRowFrame = nullptr; 5338 bool reset = false; 5339 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); 5340 rowB++) { 5341 info.IncrementRow(rowB == info.mRowIndex); 5342 BCCellBorder currentBorder = info.GetIEndEdgeBorder(); 5343 // Update/store the bStart-iEnd & bEnd-iEnd corners. Note that we 5344 // overwrite all corner information to the end of the column span. 5345 BCCornerInfo& bStartIEndCorner = 5346 (0 == rowB) ? bStartCorners[info.GetCellEndColIndex() + 1] 5347 : bEndCorners[info.GetCellEndColIndex() + 1]; 5348 bStartIEndCorner.Update(LogicalSide::BEnd, currentBorder); 5349 tableCellMap->SetBCBorderCorner( 5350 LogicalCorner::BStartIEnd, *iter.mCellMap, iter.mRowGroupStart, 5351 rowB, info.GetCellEndColIndex(), 5352 LogicalSide(bStartIEndCorner.ownerSide), bStartIEndCorner.subWidth, 5353 bStartIEndCorner.bevel); 5354 BCCornerInfo& bEndIEndCorner = 5355 bEndCorners[info.GetCellEndColIndex() + 1]; 5356 bEndIEndCorner.Set(LogicalSide::BStart, currentBorder); 5357 tableCellMap->SetBCBorderCorner( 5358 LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB, 5359 info.GetCellEndColIndex(), LogicalSide(bEndIEndCorner.ownerSide), 5360 bEndIEndCorner.subWidth, bEndIEndCorner.bevel); 5361 // update lastBlockDirBorders and see if a new segment starts 5362 bool startSeg = SetBorder( 5363 currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]); 5364 // store the border segment in the cell map and update cellBorders 5365 tableCellMap->SetBCBorderEdge( 5366 LogicalSide::IEnd, *iter.mCellMap, iter.mRowGroupStart, rowB, 5367 info.GetCellEndColIndex(), 1, currentBorder.owner, 5368 currentBorder.width, startSeg); 5369 // Set border width at inline-end (table-wide and for the cell), but 5370 // only if it's the largest we've encountered. 5371 tableInfo.SetTableIEndBorderWidth(currentBorder.width); 5372 if (!reset) { 5373 info.ResetIEndBorderWidths(); 5374 reset = true; 5375 } 5376 info.SetIEndBorderWidths(currentBorder.width); 5377 } 5378 } else { 5379 // Cell entries, but not on the block-end side of the entire table. 5380 int32_t segLength = 0; 5381 BCMapCellInfo ajaInfo(this); 5382 BCMapCellInfo priorAjaInfo(this); 5383 bool reset = false; 5384 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); 5385 rowB += segLength) { 5386 // Grab the cell adjacent to our inline-end. 5387 iter.PeekIEnd(info, rowB, ajaInfo); 5388 BCCellBorder currentBorder = info.GetIEndInternalBorder(); 5389 BCCellBorder adjacentBorder = ajaInfo.GetIStartInternalBorder(); 5390 currentBorder = CompareBorders(!CELL_CORNER, currentBorder, 5391 adjacentBorder, !INLINE_DIR); 5392 5393 segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowB); 5394 segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowB); 5395 5396 // update lastBlockDirBorders and see if a new segment starts 5397 bool startSeg = SetBorder( 5398 currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]); 5399 // store the border segment in the cell map and update cellBorders 5400 if (info.GetCellEndColIndex() < damageArea.EndCol() && 5401 rowB >= damageArea.StartRow() && rowB < damageArea.EndRow()) { 5402 tableCellMap->SetBCBorderEdge( 5403 LogicalSide::IEnd, *iter.mCellMap, iter.mRowGroupStart, rowB, 5404 info.GetCellEndColIndex(), segLength, currentBorder.owner, 5405 currentBorder.width, startSeg); 5406 if (!reset) { 5407 info.ResetIEndBorderWidths(); 5408 ajaInfo.ResetIStartBorderWidths(); 5409 reset = true; 5410 } 5411 info.SetIEndBorderWidths(currentBorder.width); 5412 ajaInfo.SetIStartBorderWidths(currentBorder.width); 5413 } 5414 // Does the block-start inline-end corner hit the inline-end adjacent 5415 // cell that wouldn't have an inline border? e.g. 5416 // 5417 // o-----------o---------------o 5418 // | | | 5419 // o-----------x Adjacent cell o 5420 // | This Cell | (rowspan) | 5421 // o-----------o---------------o 5422 bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) && 5423 (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan); 5424 BCCornerInfo* bStartIEndCorner = 5425 ((0 == rowB) || hitsSpanOnIEnd) 5426 ? &bStartCorners[info.GetCellEndColIndex() + 1] 5427 : &bEndCorners[info.GetCellEndColIndex() + 5428 1]; // From previous row. 5429 bStartIEndCorner->Update(LogicalSide::BEnd, currentBorder); 5430 // If this is a rowspan, need to consider if this "corner" is generating 5431 // an inline segment for the adjacent cell. e.g. 5432 // 5433 // o--------------o----o 5434 // | | | 5435 // o x----o 5436 // | (This "row") | | 5437 // o--------------o----o 5438 if (rowB != info.mRowIndex) { 5439 currentBorder = priorAjaInfo.GetBEndInternalBorder(); 5440 BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder(); 5441 currentBorder = CompareBorders(!CELL_CORNER, currentBorder, 5442 adjacentBorder, INLINE_DIR); 5443 bStartIEndCorner->Update(LogicalSide::IEnd, currentBorder); 5444 } 5445 // Check that the spanned area is inside of the invalidation area 5446 if (info.GetCellEndColIndex() < damageArea.EndCol() && 5447 rowB >= damageArea.StartRow()) { 5448 if (0 != rowB) { 5449 // Ok, actually store the information 5450 tableCellMap->SetBCBorderCorner( 5451 LogicalCorner::BStartIEnd, *iter.mCellMap, iter.mRowGroupStart, 5452 rowB, info.GetCellEndColIndex(), 5453 LogicalSide(bStartIEndCorner->ownerSide), 5454 bStartIEndCorner->subWidth, bStartIEndCorner->bevel); 5455 } 5456 // Propagate this segment down the rowspan 5457 for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) { 5458 tableCellMap->SetBCBorderCorner( 5459 LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart, 5460 rX, info.GetCellEndColIndex(), 5461 LogicalSide(bStartIEndCorner->ownerSide), 5462 bStartIEndCorner->subWidth, false); 5463 } 5464 } 5465 hitsSpanOnIEnd = 5466 (rowB + segLength < ajaInfo.mRowIndex + ajaInfo.mRowSpan); 5467 BCCornerInfo& bEndIEndCorner = 5468 (hitsSpanOnIEnd) ? bStartCorners[info.GetCellEndColIndex() + 1] 5469 : bEndCorners[info.GetCellEndColIndex() + 1]; 5470 bEndIEndCorner.Set(LogicalSide::BStart, currentBorder); 5471 priorAjaInfo = ajaInfo; 5472 } 5473 } 5474 for (int32_t colIdx = info.mColIndex + 1; 5475 colIdx <= info.GetCellEndColIndex(); colIdx++) { 5476 lastBlockDirBorders[colIdx].Reset(0, 1); 5477 } 5478 5479 // find the dominant border considering the cell's bEnd border, adjacent 5480 // cells and the table, row group, row 5481 if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) { 5482 // touches bEnd edge of table 5483 uint8_t idxBEnd = static_cast<uint8_t>(LogicalSide::BEnd); 5484 if (!tableBorderReset[idxBEnd]) { 5485 tableInfo.ResetTableBEndBorderWidth(); 5486 tableBorderReset[idxBEnd] = true; 5487 } 5488 bool reset = false; 5489 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex(); 5490 colIdx++) { 5491 info.SetColumn(colIdx); 5492 BCCellBorder currentBorder = info.GetBEndEdgeBorder(); 5493 BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx]; 5494 bEndIStartCorner.Update(LogicalSide::IEnd, currentBorder); 5495 tableCellMap->SetBCBorderCorner( 5496 LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart, 5497 info.GetCellEndRowIndex(), colIdx, 5498 LogicalSide(bEndIStartCorner.ownerSide), bEndIStartCorner.subWidth, 5499 bEndIStartCorner.bevel); 5500 BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + 1]; 5501 bEndIEndCorner.Update(LogicalSide::IStart, currentBorder); 5502 // Store the block-end inline-end corner if it also is the block-end 5503 // inline-end of the overall table. 5504 if (info.mNumTableCols == colIdx + 1) { 5505 tableCellMap->SetBCBorderCorner( 5506 LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart, 5507 info.GetCellEndRowIndex(), colIdx, 5508 LogicalSide(bEndIEndCorner.ownerSide), bEndIEndCorner.subWidth, 5509 bEndIEndCorner.bevel, true); 5510 } 5511 // update lastBEndBorder and see if a new segment starts 5512 bool startSeg = 5513 SetInlineDirBorder(currentBorder, bEndIStartCorner, lastBEndBorder); 5514 if (!startSeg) { 5515 // make sure that we did not compare apples to oranges i.e. the 5516 // current border should be a continuation of the lastBEndBorder, 5517 // as it is a bEnd border 5518 // add 1 to the info.GetCellEndRowIndex() 5519 startSeg = 5520 (lastBEndBorder.rowIndex != (info.GetCellEndRowIndex() + 1)); 5521 } 5522 // store the border segment in the cell map and update cellBorders 5523 tableCellMap->SetBCBorderEdge( 5524 LogicalSide::BEnd, *iter.mCellMap, iter.mRowGroupStart, 5525 info.GetCellEndRowIndex(), colIdx, 1, currentBorder.owner, 5526 currentBorder.width, startSeg); 5527 // update lastBEndBorders 5528 lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1; 5529 lastBEndBorder.rowSpan = info.mRowSpan; 5530 lastBEndBorders[colIdx] = lastBEndBorder; 5531 5532 // Set border width at block-end (table-wide and for the cell), but 5533 // only if it's the largest we've encountered. 5534 if (!reset) { 5535 info.ResetBEndBorderWidths(); 5536 reset = true; 5537 } 5538 info.SetBEndBorderWidths(currentBorder.width); 5539 tableInfo.SetTableBEndBorderWidth(currentBorder.width); 5540 } 5541 } else { 5542 int32_t segLength = 0; 5543 BCMapCellInfo ajaInfo(this); 5544 bool reset = false; 5545 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex(); 5546 colIdx += segLength) { 5547 // Grab the cell adjacent to our block-end. 5548 iter.PeekBEnd(info, colIdx, ajaInfo); 5549 BCCellBorder currentBorder = info.GetBEndInternalBorder(); 5550 BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder(); 5551 currentBorder = CompareBorders(!CELL_CORNER, currentBorder, 5552 adjacentBorder, INLINE_DIR); 5553 segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colIdx); 5554 segLength = 5555 std::min(segLength, info.mColIndex + info.mColSpan - colIdx); 5556 5557 BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx]; 5558 // e.g. 5559 // o--o----------o 5560 // | | This col | 5561 // o--x----------o 5562 // | Adjacent | 5563 // o--o----------o 5564 bool hitsSpanBelow = (colIdx > ajaInfo.mColIndex) && 5565 (colIdx < ajaInfo.mColIndex + ajaInfo.mColSpan); 5566 bool update = true; 5567 if (colIdx == info.mColIndex && colIdx > damageArea.StartCol()) { 5568 int32_t prevRowIndex = lastBEndBorders[colIdx - 1].rowIndex; 5569 if (prevRowIndex > info.GetCellEndRowIndex() + 1) { 5570 // hits a rowspan on the iEnd side 5571 update = false; 5572 // the corner was taken care of during the cell on the iStart side 5573 } else if (prevRowIndex < info.GetCellEndRowIndex() + 1) { 5574 // spans below the cell to the iStart side 5575 bStartCorners[colIdx] = bEndIStartCorner; 5576 bEndIStartCorner.Set(LogicalSide::IEnd, currentBorder); 5577 update = false; 5578 } 5579 } 5580 if (update) { 5581 bEndIStartCorner.Update(LogicalSide::IEnd, currentBorder); 5582 } 5583 // Check that the spanned area is inside of the invalidation area 5584 if (info.GetCellEndRowIndex() < damageArea.EndRow() && 5585 colIdx >= damageArea.StartCol()) { 5586 if (hitsSpanBelow) { 5587 tableCellMap->SetBCBorderCorner( 5588 LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart, 5589 info.GetCellEndRowIndex(), colIdx, 5590 LogicalSide(bEndIStartCorner.ownerSide), 5591 bEndIStartCorner.subWidth, bEndIStartCorner.bevel); 5592 } 5593 // Propagate this segment down the colspan 5594 for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) { 5595 BCCornerInfo& corner = bEndCorners[c]; 5596 corner.Set(LogicalSide::IEnd, currentBorder); 5597 tableCellMap->SetBCBorderCorner( 5598 LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart, 5599 info.GetCellEndRowIndex(), c, LogicalSide(corner.ownerSide), 5600 corner.subWidth, false); 5601 } 5602 } 5603 // update lastBEndBorders and see if a new segment starts 5604 bool startSeg = 5605 SetInlineDirBorder(currentBorder, bEndIStartCorner, lastBEndBorder); 5606 if (!startSeg) { 5607 // make sure that we did not compare apples to oranges i.e. the 5608 // current border should be a continuation of the lastBEndBorder, 5609 // as it is a bEnd border 5610 // add 1 to the info.GetCellEndRowIndex() 5611 startSeg = (lastBEndBorder.rowIndex != info.GetCellEndRowIndex() + 1); 5612 } 5613 lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1; 5614 lastBEndBorder.rowSpan = info.mRowSpan; 5615 for (int32_t c = colIdx; c < colIdx + segLength; c++) { 5616 lastBEndBorders[c] = lastBEndBorder; 5617 } 5618 5619 // store the border segment the cell map and update cellBorders 5620 if (info.GetCellEndRowIndex() < damageArea.EndRow() && 5621 colIdx >= damageArea.StartCol() && colIdx < damageArea.EndCol()) { 5622 tableCellMap->SetBCBorderEdge( 5623 LogicalSide::BEnd, *iter.mCellMap, iter.mRowGroupStart, 5624 info.GetCellEndRowIndex(), colIdx, segLength, currentBorder.owner, 5625 currentBorder.width, startSeg); 5626 5627 if (!reset) { 5628 info.ResetBEndBorderWidths(); 5629 ajaInfo.ResetBStartBorderWidths(); 5630 reset = true; 5631 } 5632 info.SetBEndBorderWidths(currentBorder.width); 5633 ajaInfo.SetBStartBorderWidths(currentBorder.width); 5634 } 5635 // update bEnd-iEnd corner 5636 BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + segLength]; 5637 bEndIEndCorner.Update(LogicalSide::IStart, currentBorder); 5638 } 5639 } 5640 // o------o------o 5641 // | c1 | | 5642 // o------o c2 o 5643 // | c3 | | 5644 // o--e1--o--e2--o 5645 // We normally join edges of successive block-end inline segments by 5646 // consulting the previous segment; however, cell c2's block-end inline 5647 // segment e2 is processed before e1, so we need to process such joins 5648 // out-of-band here, when we're processing c3. 5649 const auto nextColIndex = info.GetCellEndColIndex() + 1; 5650 if ((info.mNumTableCols != nextColIndex) && 5651 (lastBEndBorders[nextColIndex].rowSpan > 1) && 5652 (lastBEndBorders[nextColIndex].rowIndex == 5653 info.GetCellEndRowIndex() + 1)) { 5654 BCCornerInfo& corner = bEndCorners[nextColIndex]; 5655 if (!IsBlock(LogicalSide(corner.ownerSide))) { 5656 // not a block-dir owner 5657 BCCellBorder& thisBorder = lastBEndBorder; 5658 BCCellBorder& nextBorder = lastBEndBorders[info.mColIndex + 1]; 5659 if ((thisBorder.color == nextBorder.color) && 5660 (thisBorder.width == nextBorder.width) && 5661 (thisBorder.style == nextBorder.style)) { 5662 // set the flag on the next border indicating it is not the start of a 5663 // new segment 5664 if (iter.mCellMap) { 5665 tableCellMap->ResetBStartStart( 5666 LogicalSide::BEnd, *iter.mCellMap, iter.mRowGroupStart, 5667 info.GetCellEndRowIndex(), nextColIndex); 5668 } 5669 } 5670 } 5671 } 5672 } // for (iter.First(info); info.mCell; iter.Next(info)) { 5673 // reset the bc flag and damage area 5674 SetNeedToCalcBCBorders(false); 5675 propData->mDamageArea = TableArea(0, 0, 0, 0); 5676 #ifdef DEBUG_TABLE_CELLMAP 5677 mCellMap->Dump(); 5678 #endif 5679 } 5680 5681 class BCPaintBorderIterator; 5682 5683 struct BCBorderParameters { 5684 StyleBorderStyle mBorderStyle; 5685 nscolor mBorderColor; 5686 nsRect mBorderRect; 5687 mozilla::Side mStartBevelSide; 5688 nscoord mStartBevelOffset; 5689 mozilla::Side mEndBevelSide; 5690 nscoord mEndBevelOffset; 5691 bool mBackfaceIsVisible; 5692 5693 bool NeedToBevel() const { 5694 if (!mStartBevelOffset && !mEndBevelOffset) { 5695 return false; 5696 } 5697 5698 if (mBorderStyle == StyleBorderStyle::Dashed || 5699 mBorderStyle == StyleBorderStyle::Dotted) { 5700 return false; 5701 } 5702 5703 return true; 5704 } 5705 }; 5706 5707 struct BCBlockDirSeg { 5708 BCBlockDirSeg(); 5709 5710 void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner, 5711 nscoord aBlockSegISize, nscoord aInlineSegBSize, 5712 Maybe<nscoord> aEmptyRowEndSize); 5713 5714 void Initialize(BCPaintBorderIterator& aIter); 5715 void GetBEndCorner(BCPaintBorderIterator& aIter, nscoord aInlineSegBSize); 5716 5717 Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter, 5718 nscoord aInlineSegBSize); 5719 void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget, 5720 nscoord aInlineSegBSize); 5721 void CreateWebRenderCommands(BCPaintBorderIterator& aIter, 5722 nscoord aInlineSegBSize, 5723 wr::DisplayListBuilder& aBuilder, 5724 const layers::StackingContextHelper& aSc, 5725 const nsPoint& aPt); 5726 void AdvanceOffsetB(); 5727 void IncludeCurrentBorder(BCPaintBorderIterator& aIter); 5728 5729 union { 5730 nsTableColFrame* mCol; 5731 int32_t mColWidth; 5732 }; 5733 nscoord mOffsetI; // i-offset with respect to the table edge 5734 nscoord mOffsetB; // b-offset with respect to the table edge 5735 nscoord mLength; // block-dir length including corners 5736 nscoord mWidth; // thickness 5737 5738 nsTableCellFrame* mAjaCell; // previous sibling to the first cell 5739 // where the segment starts, it can be 5740 // the owner of a segment 5741 nsTableCellFrame* mFirstCell; // cell at the start of the segment 5742 nsTableRowGroupFrame* 5743 mFirstRowGroup; // row group at the start of the segment 5744 nsTableRowFrame* mFirstRow; // row at the start of the segment 5745 nsTableCellFrame* mLastCell; // cell at the current end of the 5746 // segment 5747 5748 uint8_t mOwner; // owner of the border, defines the 5749 // style 5750 LogicalSide mBStartBevelSide; // direction to bevel at the bStart 5751 nscoord mBStartBevelOffset; // how much to bevel at the bStart 5752 nscoord mBEndInlineSegBSize; // bSize of the crossing 5753 // inline-dir border 5754 nscoord mBEndOffset; // how much longer is the segment due 5755 // to the inline-dir border, by this 5756 // amount the next segment needs to be 5757 // shifted. 5758 bool mIsBEndBevel; // should we bevel at the bEnd 5759 }; 5760 5761 struct BCInlineDirSeg { 5762 BCInlineDirSeg(); 5763 5764 void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner, 5765 nscoord aBEndBlockSegISize, nscoord aInlineSegBSize); 5766 void GetIEndCorner(BCPaintBorderIterator& aIter, nscoord aIStartSegISize); 5767 void AdvanceOffsetI(); 5768 void IncludeCurrentBorder(BCPaintBorderIterator& aIter); 5769 Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter); 5770 void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget); 5771 void CreateWebRenderCommands(BCPaintBorderIterator& aIter, 5772 wr::DisplayListBuilder& aBuilder, 5773 const layers::StackingContextHelper& aSc, 5774 const nsPoint& aPt); 5775 5776 nscoord mOffsetI; // i-offset with respect to the table edge 5777 nscoord mOffsetB; // b-offset with respect to the table edge 5778 nscoord mLength; // inline-dir length including corners 5779 nscoord mWidth; // border thickness 5780 nscoord mIStartBevelOffset; // how much to bevel at the iStart 5781 LogicalSide mIStartBevelSide; // direction to bevel at the iStart 5782 bool mIsIEndBevel; // should we bevel at the iEnd end 5783 nscoord mIEndBevelOffset; // how much to bevel at the iEnd 5784 LogicalSide mIEndBevelSide; // direction to bevel at the iEnd 5785 nscoord mEndOffset; // how much longer is the segment due 5786 // to the block-dir border, by this 5787 // amount the next segment needs to be 5788 // shifted. 5789 uint8_t mOwner; // owner of the border, defines the 5790 // style 5791 nsTableCellFrame* mFirstCell; // cell at the start of the segment 5792 nsTableCellFrame* mAjaCell; // neighboring cell to the first cell 5793 // where the segment starts, it can be 5794 // the owner of a segment 5795 }; 5796 5797 struct BCPaintData { 5798 explicit BCPaintData(DrawTarget& aDrawTarget) : mDrawTarget(aDrawTarget) {} 5799 5800 DrawTarget& mDrawTarget; 5801 }; 5802 5803 struct BCCreateWebRenderCommandsData { 5804 BCCreateWebRenderCommandsData(wr::DisplayListBuilder& aBuilder, 5805 const layers::StackingContextHelper& aSc, 5806 const nsPoint& aOffsetToReferenceFrame) 5807 : mBuilder(aBuilder), 5808 mSc(aSc), 5809 mOffsetToReferenceFrame(aOffsetToReferenceFrame) {} 5810 5811 wr::DisplayListBuilder& mBuilder; 5812 const layers::StackingContextHelper& mSc; 5813 const nsPoint& mOffsetToReferenceFrame; 5814 }; 5815 5816 struct BCPaintBorderAction { 5817 explicit BCPaintBorderAction(DrawTarget& aDrawTarget) 5818 : mMode(Mode::Paint), mPaintData(aDrawTarget) {} 5819 5820 BCPaintBorderAction(wr::DisplayListBuilder& aBuilder, 5821 const layers::StackingContextHelper& aSc, 5822 const nsPoint& aOffsetToReferenceFrame) 5823 : mMode(Mode::CreateWebRenderCommands), 5824 mCreateWebRenderCommandsData(aBuilder, aSc, aOffsetToReferenceFrame) {} 5825 5826 ~BCPaintBorderAction() { 5827 // mCreateWebRenderCommandsData is in a union which means the destructor 5828 // wouldn't be called when BCPaintBorderAction get destroyed. So call the 5829 // destructor here explicitly. 5830 if (mMode == Mode::CreateWebRenderCommands) { 5831 mCreateWebRenderCommandsData.~BCCreateWebRenderCommandsData(); 5832 } 5833 } 5834 5835 enum class Mode { 5836 Paint, 5837 CreateWebRenderCommands, 5838 }; 5839 5840 Mode mMode; 5841 5842 union { 5843 BCPaintData mPaintData; 5844 BCCreateWebRenderCommandsData mCreateWebRenderCommandsData; 5845 }; 5846 }; 5847 5848 // Iterates over borders (iStart border, corner, bStart border) in the cell map 5849 // within a damage area from iStart to iEnd, bStart to bEnd. All members are in 5850 // terms of the 1st in flow frames, except where suffixed by InFlow. 5851 class BCPaintBorderIterator { 5852 public: 5853 explicit BCPaintBorderIterator(nsTableFrame* aTable); 5854 void Reset(); 5855 5856 /** 5857 * Determine the damage area in terms of rows and columns and finalize 5858 * mInitialOffsetI and mInitialOffsetB. 5859 * @param aDirtyRect - dirty rect in table coordinates 5860 * @return - true if we need to paint something given dirty rect 5861 */ 5862 bool SetDamageArea(const nsRect& aDamageRect); 5863 void First(); 5864 void Next(); 5865 void AccumulateOrDoActionInlineDirSegment(BCPaintBorderAction& aAction); 5866 void AccumulateOrDoActionBlockDirSegment(BCPaintBorderAction& aAction); 5867 void ResetVerInfo(); 5868 void StoreColumnWidth(int32_t aIndex); 5869 bool BlockDirSegmentOwnsCorner(); 5870 5871 nsTableFrame* mTable; 5872 nsTableFrame* mTableFirstInFlow; 5873 nsTableCellMap* mTableCellMap; 5874 nsCellMap* mCellMap; 5875 WritingMode mTableWM; 5876 nsTableFrame::RowGroupArray mRowGroups; 5877 5878 nsTableRowGroupFrame* mPrevRg; 5879 nsTableRowGroupFrame* mRg; 5880 bool mIsRepeatedHeader; 5881 bool mIsRepeatedFooter; 5882 nsTableRowGroupFrame* mStartRg; // first row group in the damagearea 5883 int32_t mRgIndex; // current row group index in the 5884 // mRowgroups array 5885 int32_t mFifRgFirstRowIndex; // start row index of the first in 5886 // flow of the row group 5887 int32_t mRgFirstRowIndex; // row index of the first row in the 5888 // row group 5889 int32_t mRgLastRowIndex; // row index of the last row in the row 5890 // group 5891 int32_t mNumTableRows; // number of rows in the table and all 5892 // continuations 5893 int32_t mNumTableCols; // number of columns in the table 5894 int32_t mColIndex; // with respect to the table 5895 int32_t mRowIndex; // with respect to the table 5896 int32_t mRepeatedHeaderRowIndex; // row index in a repeated 5897 // header, it's equivalent to 5898 // mRowIndex when we're in a repeated 5899 // header, and set to the last row 5900 // index of a repeated header when 5901 // we're not 5902 bool mIsNewRow; 5903 bool mAtEnd; // the iterator cycled over all 5904 // borders 5905 nsTableRowFrame* mPrevRow; 5906 nsTableRowFrame* mRow; 5907 nsTableRowFrame* mStartRow; // first row in a inside the damagearea 5908 5909 // cell properties 5910 nsTableCellFrame* mPrevCell; 5911 nsTableCellFrame* mCell; 5912 BCCellData* mPrevCellData; 5913 BCCellData* mCellData; 5914 BCData* mBCData; 5915 5916 bool IsTableBStartMost() { 5917 return (mRowIndex == 0) && !mTable->GetPrevInFlow(); 5918 } 5919 bool IsTableIEndMost() { return (mColIndex >= mNumTableCols); } 5920 bool IsTableBEndMost() { 5921 return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow(); 5922 } 5923 bool IsTableIStartMost() { return (mColIndex == 0); } 5924 bool IsDamageAreaBStartMost() const { 5925 return mRowIndex == mDamageArea.StartRow(); 5926 } 5927 bool IsDamageAreaIEndMost() const { 5928 return mColIndex >= mDamageArea.EndCol(); 5929 } 5930 bool IsDamageAreaBEndMost() const { 5931 return mRowIndex >= mDamageArea.EndRow(); 5932 } 5933 bool IsDamageAreaIStartMost() const { 5934 return mColIndex == mDamageArea.StartCol(); 5935 } 5936 int32_t GetRelativeColIndex() const { 5937 return mColIndex - mDamageArea.StartCol(); 5938 } 5939 5940 TableArea mDamageArea; // damageArea in cellmap coordinates 5941 bool IsAfterRepeatedHeader() { 5942 return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1)); 5943 } 5944 bool StartRepeatedFooter() const { 5945 return mIsRepeatedFooter && mRowIndex == mRgFirstRowIndex && 5946 mRowIndex != mDamageArea.StartRow(); 5947 } 5948 5949 nscoord mInitialOffsetI; // offsetI of the first border with 5950 // respect to the table 5951 nscoord mInitialOffsetB; // offsetB of the first border with 5952 // respect to the table 5953 nscoord mNextOffsetB; // offsetB of the next segment 5954 // this array is used differently when 5955 // inline-dir and block-dir borders are drawn 5956 // When inline-dir border are drawn we cache 5957 // the column widths and the width of the 5958 // block-dir borders that arrive from bStart 5959 // When we draw block-dir borders we store 5960 // lengths and width for block-dir borders 5961 // before they are drawn while we move over 5962 // the columns in the damage area 5963 // It has one more elements than columns are 5964 // in the table. 5965 UniquePtr<BCBlockDirSeg[]> mBlockDirInfo; 5966 BCInlineDirSeg mInlineSeg; // the inline-dir segment while we 5967 // move over the colums 5968 nscoord mPrevInlineSegBSize; // the bSize of the previous 5969 // inline-dir border 5970 5971 private: 5972 bool SetNewRow(nsTableRowFrame* aRow = nullptr); 5973 bool SetNewRowGroup(); 5974 void SetNewData(int32_t aRowIndex, int32_t aColIndex); 5975 }; 5976 5977 BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable) 5978 : mTable(aTable), 5979 mTableFirstInFlow(static_cast<nsTableFrame*>(aTable->FirstInFlow())), 5980 mTableCellMap(aTable->GetCellMap()), 5981 mCellMap(nullptr), 5982 mTableWM(aTable->Style()), 5983 mRowGroups(aTable->OrderedRowGroups()), 5984 mPrevRg(nullptr), 5985 mRg(nullptr), 5986 mIsRepeatedHeader(false), 5987 mIsRepeatedFooter(false), 5988 mStartRg(nullptr), 5989 mRgIndex(0), 5990 mFifRgFirstRowIndex(0), 5991 mRgFirstRowIndex(0), 5992 mRgLastRowIndex(0), 5993 mColIndex(0), 5994 mRowIndex(0), 5995 mIsNewRow(false), 5996 mAtEnd(false), 5997 mPrevRow(nullptr), 5998 mRow(nullptr), 5999 mStartRow(nullptr), 6000 mPrevCell(nullptr), 6001 mCell(nullptr), 6002 mPrevCellData(nullptr), 6003 mCellData(nullptr), 6004 mBCData(nullptr), 6005 mInitialOffsetI(0), 6006 mNextOffsetB(0), 6007 mPrevInlineSegBSize(0) { 6008 MOZ_ASSERT(mTable->IsBorderCollapse(), 6009 "Why are we here if the table is not border-collapsed?"); 6010 6011 const LogicalMargin bp = mTable->GetOuterBCBorder(mTableWM); 6012 // block position of first row in damage area 6013 mInitialOffsetB = mTable->GetPrevInFlow() ? 0 : bp.BStart(mTableWM); 6014 mNumTableRows = mTable->GetRowCount(); 6015 mNumTableCols = mTable->GetColCount(); 6016 6017 // initialize to a non existing index 6018 mRepeatedHeaderRowIndex = -99; 6019 } 6020 6021 bool BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) { 6022 nsSize containerSize = mTable->GetSize(); 6023 LogicalRect dirtyRect(mTableWM, aDirtyRect, containerSize); 6024 uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex; 6025 startRowIndex = endRowIndex = startColIndex = endColIndex = 0; 6026 bool done = false; 6027 bool haveIntersect = false; 6028 // find startRowIndex, endRowIndex 6029 nscoord rowB = mInitialOffsetB; 6030 for (uint32_t rgIdx = 0; rgIdx < mRowGroups.Length() && !done; rgIdx++) { 6031 nsTableRowGroupFrame* rgFrame = mRowGroups[rgIdx]; 6032 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame; 6033 rowFrame = rowFrame->GetNextRow()) { 6034 // get the row rect relative to the table rather than the row group 6035 nscoord rowBSize = rowFrame->BSize(mTableWM); 6036 const nscoord onePx = mTable->PresContext()->DevPixelsToAppUnits(1); 6037 if (haveIntersect) { 6038 // conservatively estimate the half border widths outside the row 6039 nscoord borderHalf = mTable->GetPrevInFlow() 6040 ? 0 6041 : rowFrame->GetBStartBCBorderWidth() + onePx; 6042 6043 if (dirtyRect.BEnd(mTableWM) >= rowB - borderHalf) { 6044 nsTableRowFrame* fifRow = 6045 static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow()); 6046 endRowIndex = fifRow->GetRowIndex(); 6047 } else { 6048 done = true; 6049 } 6050 } else { 6051 // conservatively estimate the half border widths outside the row 6052 nscoord borderHalf = mTable->GetNextInFlow() 6053 ? 0 6054 : rowFrame->GetBEndBCBorderWidth() + onePx; 6055 if (rowB + rowBSize + borderHalf >= dirtyRect.BStart(mTableWM)) { 6056 mStartRg = rgFrame; 6057 mStartRow = rowFrame; 6058 nsTableRowFrame* fifRow = 6059 static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow()); 6060 startRowIndex = endRowIndex = fifRow->GetRowIndex(); 6061 haveIntersect = true; 6062 } else { 6063 mInitialOffsetB += rowBSize; 6064 } 6065 } 6066 rowB += rowBSize; 6067 } 6068 } 6069 mNextOffsetB = mInitialOffsetB; 6070 6071 // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag 6072 // XXX but I don't understand it, so not changing it for now 6073 // table wrapper borders overflow the table, so the table might be 6074 // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set 6075 // on the table 6076 if (!haveIntersect) { 6077 return false; 6078 } 6079 // find startColIndex, endColIndex, startColX 6080 haveIntersect = false; 6081 if (0 == mNumTableCols) { 6082 return false; 6083 } 6084 6085 LogicalMargin bp = mTable->GetOuterBCBorder(mTableWM); 6086 6087 // inline position of first col in damage area 6088 mInitialOffsetI = bp.IStart(mTableWM); 6089 6090 nscoord x = 0; 6091 int32_t colIdx; 6092 for (colIdx = 0; colIdx != mNumTableCols; colIdx++) { 6093 nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colIdx); 6094 if (!colFrame) ABORT1(false); 6095 const nscoord onePx = mTable->PresContext()->DevPixelsToAppUnits(1); 6096 // get the col rect relative to the table rather than the col group 6097 nscoord colISize = colFrame->ISize(mTableWM); 6098 if (haveIntersect) { 6099 // conservatively estimate the iStart half border width outside the col 6100 nscoord iStartBorderHalf = colFrame->GetIStartBorderWidth() + onePx; 6101 if (dirtyRect.IEnd(mTableWM) >= x - iStartBorderHalf) { 6102 endColIndex = colIdx; 6103 } else { 6104 break; 6105 } 6106 } else { 6107 // conservatively estimate the iEnd half border width outside the col 6108 nscoord iEndBorderHalf = colFrame->GetIEndBorderWidth() + onePx; 6109 if (x + colISize + iEndBorderHalf >= dirtyRect.IStart(mTableWM)) { 6110 startColIndex = endColIndex = colIdx; 6111 haveIntersect = true; 6112 } else { 6113 mInitialOffsetI += colISize; 6114 } 6115 } 6116 x += colISize; 6117 } 6118 if (!haveIntersect) { 6119 return false; 6120 } 6121 mDamageArea = 6122 TableArea(startColIndex, startRowIndex, 6123 1 + DeprecatedAbs<int32_t>(endColIndex - startColIndex), 6124 1 + endRowIndex - startRowIndex); 6125 6126 Reset(); 6127 mBlockDirInfo = MakeUnique<BCBlockDirSeg[]>(mDamageArea.ColCount() + 1); 6128 return true; 6129 } 6130 6131 void BCPaintBorderIterator::Reset() { 6132 mAtEnd = true; // gets reset when First() is called 6133 mRg = mStartRg; 6134 mPrevRow = nullptr; 6135 mRow = mStartRow; 6136 mRowIndex = 0; 6137 mColIndex = 0; 6138 mRgIndex = -1; 6139 mPrevCell = nullptr; 6140 mCell = nullptr; 6141 mPrevCellData = nullptr; 6142 mCellData = nullptr; 6143 mBCData = nullptr; 6144 ResetVerInfo(); 6145 } 6146 6147 /** 6148 * Set the iterator data to a new cellmap coordinate 6149 * @param aRowIndex - the row index 6150 * @param aColIndex - the col index 6151 */ 6152 void BCPaintBorderIterator::SetNewData(int32_t aY, int32_t aX) { 6153 if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0(); 6154 6155 mColIndex = aX; 6156 mRowIndex = aY; 6157 mPrevCellData = mCellData; 6158 if (IsTableIEndMost() && IsTableBEndMost()) { 6159 mCell = nullptr; 6160 mBCData = &mTableCellMap->mBCInfo->mBEndIEndCorner; 6161 } else if (IsTableIEndMost()) { 6162 mCellData = nullptr; 6163 mBCData = &mTableCellMap->mBCInfo->mIEndBorders.ElementAt(aY); 6164 } else if (IsTableBEndMost()) { 6165 mCellData = nullptr; 6166 mBCData = &mTableCellMap->mBCInfo->mBEndBorders.ElementAt(aX); 6167 } else { 6168 // We should have set mCellMap during SetNewRowGroup, but if we failed to 6169 // find the appropriate map there, let's just give up. 6170 // Bailing out here may leave us with some missing borders, but seems 6171 // preferable to crashing. (Bug 1442018) 6172 if (MOZ_UNLIKELY(!mCellMap)) { 6173 ABORT0(); 6174 } 6175 if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) { 6176 mBCData = nullptr; 6177 mCellData = (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex] 6178 .SafeElementAt(mColIndex); 6179 if (mCellData) { 6180 mBCData = &mCellData->mData; 6181 if (!mCellData->IsOrig()) { 6182 if (mCellData->IsRowSpan()) { 6183 aY -= mCellData->GetRowSpanOffset(); 6184 } 6185 if (mCellData->IsColSpan()) { 6186 aX -= mCellData->GetColSpanOffset(); 6187 } 6188 if ((aX >= 0) && (aY >= 0)) { 6189 mCellData = 6190 (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX]; 6191 } 6192 } 6193 if (mCellData->IsOrig()) { 6194 mPrevCell = mCell; 6195 mCell = mCellData->GetCellFrame(); 6196 } 6197 } 6198 } 6199 } 6200 } 6201 6202 /** 6203 * Set the iterator to a new row 6204 * @param aRow - the new row frame, if null the iterator will advance to the 6205 * next row 6206 */ 6207 bool BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow) { 6208 mPrevRow = mRow; 6209 mRow = (aRow) ? aRow : mRow->GetNextRow(); 6210 if (mRow) { 6211 mIsNewRow = true; 6212 mRowIndex = mRow->GetRowIndex(); 6213 mColIndex = mDamageArea.StartCol(); 6214 mPrevInlineSegBSize = 0; 6215 if (mIsRepeatedHeader) { 6216 mRepeatedHeaderRowIndex = mRowIndex; 6217 } 6218 } else { 6219 mAtEnd = true; 6220 } 6221 return !mAtEnd; 6222 } 6223 6224 /** 6225 * Advance the iterator to the next row group 6226 */ 6227 bool BCPaintBorderIterator::SetNewRowGroup() { 6228 mRgIndex++; 6229 6230 mIsRepeatedHeader = false; 6231 mIsRepeatedFooter = false; 6232 6233 NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds"); 6234 if (uint32_t(mRgIndex) < mRowGroups.Length()) { 6235 mPrevRg = mRg; 6236 mRg = mRowGroups[mRgIndex]; 6237 nsTableRowGroupFrame* fifRg = 6238 static_cast<nsTableRowGroupFrame*>(mRg->FirstInFlow()); 6239 mFifRgFirstRowIndex = fifRg->GetStartRowIndex(); 6240 mRgFirstRowIndex = mRg->GetStartRowIndex(); 6241 mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1; 6242 6243 if (SetNewRow(mRg->GetFirstRow())) { 6244 mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr); 6245 if (!mCellMap) ABORT1(false); 6246 } 6247 if (mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) { 6248 // if mRowGroup doesn't have a prev in flow, then it may be a repeated 6249 // header or footer 6250 const nsStyleDisplay* display = mRg->StyleDisplay(); 6251 if (mRowIndex == mDamageArea.StartRow()) { 6252 mIsRepeatedHeader = 6253 (mozilla::StyleDisplay::TableHeaderGroup == display->mDisplay); 6254 } else { 6255 mIsRepeatedFooter = 6256 (mozilla::StyleDisplay::TableFooterGroup == display->mDisplay); 6257 } 6258 } 6259 } else { 6260 mAtEnd = true; 6261 } 6262 return !mAtEnd; 6263 } 6264 6265 /** 6266 * Move the iterator to the first position in the damageArea 6267 */ 6268 void BCPaintBorderIterator::First() { 6269 if (!mTable || mDamageArea.StartCol() >= mNumTableCols || 6270 mDamageArea.StartRow() >= mNumTableRows) 6271 ABORT0(); 6272 6273 mAtEnd = false; 6274 6275 uint32_t numRowGroups = mRowGroups.Length(); 6276 for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) { 6277 nsTableRowGroupFrame* rowG = mRowGroups[rgY]; 6278 int32_t start = rowG->GetStartRowIndex(); 6279 int32_t end = start + rowG->GetRowCount() - 1; 6280 if (mDamageArea.StartRow() >= start && mDamageArea.StartRow() <= end) { 6281 mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex 6282 if (SetNewRowGroup()) { 6283 while (mRowIndex < mDamageArea.StartRow() && !mAtEnd) { 6284 SetNewRow(); 6285 } 6286 if (!mAtEnd) { 6287 SetNewData(mDamageArea.StartRow(), mDamageArea.StartCol()); 6288 } 6289 } 6290 return; 6291 } 6292 } 6293 mAtEnd = true; 6294 } 6295 6296 /** 6297 * Advance the iterator to the next position 6298 */ 6299 void BCPaintBorderIterator::Next() { 6300 if (mAtEnd) ABORT0(); 6301 mIsNewRow = false; 6302 6303 mColIndex++; 6304 if (mColIndex > mDamageArea.EndCol()) { 6305 mRowIndex++; 6306 if (mRowIndex == mDamageArea.EndRow()) { 6307 mColIndex = mDamageArea.StartCol(); 6308 } else if (mRowIndex < mDamageArea.EndRow()) { 6309 if (mRowIndex <= mRgLastRowIndex) { 6310 SetNewRow(); 6311 } else { 6312 SetNewRowGroup(); 6313 } 6314 } else { 6315 mAtEnd = true; 6316 } 6317 } 6318 if (!mAtEnd) { 6319 SetNewData(mRowIndex, mColIndex); 6320 } 6321 } 6322 6323 // XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine 6324 // them 6325 // XXX Update terminology from physical to logical 6326 /** Compute the vertical offset of a vertical border segment 6327 * @param aCornerOwnerSide - which side owns the corner 6328 * @param aCornerSubWidth - how wide is the nonwinning side of the corner 6329 * @param aHorWidth - how wide is the horizontal edge of the corner 6330 * @param aIsStartOfSeg - does this corner start a new segment 6331 * @param aIsBevel - is this corner beveled 6332 * @return - offset in twips 6333 */ 6334 static nscoord CalcVerCornerOffset(LogicalSide aCornerOwnerSide, 6335 nscoord aCornerSubWidth, nscoord aHorWidth, 6336 bool aIsStartOfSeg, bool aIsBevel) { 6337 nscoord offset = 0; 6338 // XXX These should be replaced with appropriate side-specific macros (which?) 6339 nscoord smallHalf, largeHalf; 6340 if (IsBlock(aCornerOwnerSide)) { 6341 DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); 6342 if (aIsBevel) { 6343 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; 6344 } else { 6345 offset = 6346 (LogicalSide::BStart == aCornerOwnerSide) ? smallHalf : -largeHalf; 6347 } 6348 } else { 6349 DivideBCBorderSize(aHorWidth, smallHalf, largeHalf); 6350 if (aIsBevel) { 6351 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; 6352 } else { 6353 offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; 6354 } 6355 } 6356 return offset; 6357 } 6358 6359 /** Compute the horizontal offset of a horizontal border segment 6360 * @param aCornerOwnerSide - which side owns the corner 6361 * @param aCornerSubWidth - how wide is the nonwinning side of the corner 6362 * @param aVerWidth - how wide is the vertical edge of the corner 6363 * @param aIsStartOfSeg - does this corner start a new segment 6364 * @param aIsBevel - is this corner beveled 6365 * @return - offset in twips 6366 */ 6367 static nscoord CalcHorCornerOffset(LogicalSide aCornerOwnerSide, 6368 nscoord aCornerSubWidth, nscoord aVerWidth, 6369 bool aIsStartOfSeg, bool aIsBevel) { 6370 nscoord offset = 0; 6371 // XXX These should be replaced with appropriate side-specific macros (which?) 6372 nscoord smallHalf, largeHalf; 6373 if (IsInline(aCornerOwnerSide)) { 6374 DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); 6375 if (aIsBevel) { 6376 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; 6377 } else { 6378 offset = 6379 (LogicalSide::IStart == aCornerOwnerSide) ? smallHalf : -largeHalf; 6380 } 6381 } else { 6382 DivideBCBorderSize(aVerWidth, smallHalf, largeHalf); 6383 if (aIsBevel) { 6384 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; 6385 } else { 6386 offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; 6387 } 6388 } 6389 return offset; 6390 } 6391 6392 BCBlockDirSeg::BCBlockDirSeg() 6393 : mFirstRowGroup(nullptr), 6394 mFirstRow(nullptr), 6395 mBEndInlineSegBSize(0), 6396 mBEndOffset(0), 6397 mIsBEndBevel(false) { 6398 mCol = nullptr; 6399 mFirstCell = mLastCell = mAjaCell = nullptr; 6400 mOffsetI = mOffsetB = mLength = mWidth = mBStartBevelOffset = 0; 6401 mBStartBevelSide = LogicalSide::BStart; 6402 mOwner = eCellOwner; 6403 } 6404 6405 /** 6406 * Start a new block-direction segment 6407 * @param aIter - iterator containing the structural information 6408 * @param aBorderOwner - determines the border style 6409 * @param aBlockSegISize - the width of segment 6410 * @param aInlineSegBSize - the width of the inline-dir segment joining the 6411 * corner at the start 6412 */ 6413 void BCBlockDirSeg::Start(BCPaintBorderIterator& aIter, 6414 BCBorderOwner aBorderOwner, nscoord aBlockSegISize, 6415 nscoord aInlineSegBSize, 6416 Maybe<nscoord> aEmptyRowEndBSize) { 6417 LogicalSide ownerSide = LogicalSide::BStart; 6418 bool bevel = false; 6419 6420 nscoord cornerSubWidth = 6421 (aIter.mBCData) ? aIter.mBCData->GetCorner(ownerSide, bevel) : 0; 6422 6423 bool bStartBevel = (aBlockSegISize > 0) ? bevel : false; 6424 nscoord maxInlineSegBSize = 6425 std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize); 6426 nscoord offset = CalcVerCornerOffset(ownerSide, cornerSubWidth, 6427 maxInlineSegBSize, true, bStartBevel); 6428 6429 mBStartBevelOffset = bStartBevel ? maxInlineSegBSize : 0; 6430 // XXX this assumes that only corners where 2 segments join can be beveled 6431 mBStartBevelSide = 6432 (aInlineSegBSize > 0) ? LogicalSide::IEnd : LogicalSide::IStart; 6433 if (aEmptyRowEndBSize && *aEmptyRowEndBSize < offset) { 6434 // This segment is starting from an empty row. This will require the the 6435 // starting segment to overlap with the previously drawn segment, unless the 6436 // empty row's size clears the overlap. 6437 mOffsetB += *aEmptyRowEndBSize; 6438 } else { 6439 mOffsetB += offset; 6440 } 6441 mLength = -offset; 6442 mWidth = aBlockSegISize; 6443 mOwner = aBorderOwner; 6444 mFirstCell = aIter.mCell; 6445 mFirstRowGroup = aIter.mRg; 6446 mFirstRow = aIter.mRow; 6447 if (aIter.GetRelativeColIndex() > 0) { 6448 mAjaCell = aIter.mBlockDirInfo[aIter.GetRelativeColIndex() - 1].mLastCell; 6449 } 6450 } 6451 6452 /** 6453 * Initialize the block-dir segments with information that will persist for any 6454 * block-dir segment in this column 6455 * @param aIter - iterator containing the structural information 6456 */ 6457 void BCBlockDirSeg::Initialize(BCPaintBorderIterator& aIter) { 6458 int32_t relColIndex = aIter.GetRelativeColIndex(); 6459 mCol = aIter.IsTableIEndMost() 6460 ? aIter.mBlockDirInfo[relColIndex - 1].mCol 6461 : aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex); 6462 if (!mCol) ABORT0(); 6463 if (0 == relColIndex) { 6464 mOffsetI = aIter.mInitialOffsetI; 6465 } 6466 // set mOffsetI for the next column 6467 if (!aIter.IsDamageAreaIEndMost()) { 6468 aIter.mBlockDirInfo[relColIndex + 1].mOffsetI = 6469 mOffsetI + mCol->ISize(aIter.mTableWM); 6470 } 6471 mOffsetB = aIter.mInitialOffsetB; 6472 mLastCell = aIter.mCell; 6473 } 6474 6475 /** 6476 * Compute the offsets for the bEnd corner of a block-dir segment 6477 * @param aIter - iterator containing the structural information 6478 * @param aInlineSegBSize - the width of the inline-dir segment joining the 6479 * corner at the start 6480 */ 6481 void BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator& aIter, 6482 nscoord aInlineSegBSize) { 6483 LogicalSide ownerSide = LogicalSide::BStart; 6484 nscoord cornerSubWidth = 0; 6485 bool bevel = false; 6486 if (aIter.mBCData) { 6487 cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); 6488 } 6489 mIsBEndBevel = (mWidth > 0) ? bevel : false; 6490 mBEndInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize); 6491 mBEndOffset = CalcVerCornerOffset(ownerSide, cornerSubWidth, 6492 mBEndInlineSegBSize, false, mIsBEndBevel); 6493 mLength += mBEndOffset; 6494 } 6495 6496 Maybe<BCBorderParameters> BCBlockDirSeg::BuildBorderParameters( 6497 BCPaintBorderIterator& aIter, nscoord aInlineSegBSize) { 6498 BCBorderParameters result; 6499 6500 // get the border style, color and paint the segment 6501 LogicalSide side = 6502 aIter.IsDamageAreaIEndMost() ? LogicalSide::IEnd : LogicalSide::IStart; 6503 int32_t relColIndex = aIter.GetRelativeColIndex(); 6504 nsTableColFrame* col = mCol; 6505 if (!col) ABORT1(Nothing()); 6506 nsTableCellFrame* cell = mFirstCell; // ??? 6507 nsIFrame* owner = nullptr; 6508 result.mBorderStyle = StyleBorderStyle::Solid; 6509 result.mBorderColor = 0xFFFFFFFF; 6510 result.mBackfaceIsVisible = true; 6511 6512 switch (mOwner) { 6513 case eTableOwner: 6514 owner = aIter.mTable; 6515 break; 6516 case eAjaColGroupOwner: 6517 side = LogicalSide::IEnd; 6518 if (!aIter.IsTableIEndMost() && (relColIndex > 0)) { 6519 col = aIter.mBlockDirInfo[relColIndex - 1].mCol; 6520 } 6521 [[fallthrough]]; 6522 case eColGroupOwner: 6523 if (col) { 6524 owner = col->GetParent(); 6525 } 6526 break; 6527 case eAjaColOwner: 6528 side = LogicalSide::IEnd; 6529 if (!aIter.IsTableIEndMost() && (relColIndex > 0)) { 6530 col = aIter.mBlockDirInfo[relColIndex - 1].mCol; 6531 } 6532 [[fallthrough]]; 6533 case eColOwner: 6534 owner = col; 6535 break; 6536 case eAjaRowGroupOwner: 6537 NS_ERROR("a neighboring rowgroup can never own a vertical border"); 6538 [[fallthrough]]; 6539 case eRowGroupOwner: 6540 NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(), 6541 "row group can own border only at table edge"); 6542 owner = mFirstRowGroup; 6543 break; 6544 case eAjaRowOwner: 6545 NS_ERROR("program error"); 6546 [[fallthrough]]; 6547 case eRowOwner: 6548 NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(), 6549 "row can own border only at table edge"); 6550 owner = mFirstRow; 6551 break; 6552 case eAjaCellOwner: 6553 side = LogicalSide::IEnd; 6554 cell = mAjaCell; 6555 [[fallthrough]]; 6556 case eCellOwner: 6557 owner = cell; 6558 break; 6559 } 6560 if (owner) { 6561 ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle, 6562 &result.mBorderColor); 6563 result.mBackfaceIsVisible = !owner->BackfaceIsHidden(); 6564 } 6565 nscoord smallHalf, largeHalf; 6566 DivideBCBorderSize(mWidth, smallHalf, largeHalf); 6567 LogicalRect segRect(aIter.mTableWM, mOffsetI - largeHalf, mOffsetB, mWidth, 6568 mLength); 6569 nscoord bEndBevelOffset = mIsBEndBevel ? mBEndInlineSegBSize : 0; 6570 LogicalSide bEndBevelSide = 6571 (aInlineSegBSize > 0) ? LogicalSide::IEnd : LogicalSide::IStart; 6572 6573 // Convert logical to physical sides/coordinates for DrawTableBorderSegment. 6574 6575 result.mBorderRect = 6576 segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize()); 6577 // XXX For reversed vertical writing-modes (with direction:rtl), we need to 6578 // invert physicalRect's y-position here, with respect to the table. 6579 // However, it's not worth fixing the border positions here until the 6580 // ordering of the table columns themselves is also fixed (bug 1180528). 6581 6582 result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide); 6583 result.mEndBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide); 6584 result.mStartBevelOffset = mBStartBevelOffset; 6585 result.mEndBevelOffset = bEndBevelOffset; 6586 // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal) 6587 // border segment need to be swapped because DrawTableBorderSegment will 6588 // apply the 'start' bevel at the left edge, and 'end' at the right. 6589 // (Note: In this case, startBevelSide/endBevelSide will usually both be 6590 // "top" or "bottom". DrawTableBorderSegment works purely with physical 6591 // coordinates, so it expects startBevelOffset to be the indentation-from- 6592 // the-left for the "start" (left) end of the border-segment, and 6593 // endBevelOffset is the indentation-from-the-right for the "end" (right) 6594 // end of the border-segment. We've got them reversed, since our block dir 6595 // is RTL, so we have to swap them here.) 6596 if (aIter.mTableWM.IsVerticalRL()) { 6597 std::swap(result.mStartBevelSide, result.mEndBevelSide); 6598 std::swap(result.mStartBevelOffset, result.mEndBevelOffset); 6599 } 6600 6601 return Some(result); 6602 } 6603 6604 /** 6605 * Paint the block-dir segment 6606 * @param aIter - iterator containing the structural information 6607 * @param aDrawTarget - the draw target 6608 * @param aInlineSegBSize - the width of the inline-dir segment joining the 6609 * corner at the start 6610 */ 6611 void BCBlockDirSeg::Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget, 6612 nscoord aInlineSegBSize) { 6613 Maybe<BCBorderParameters> param = 6614 BuildBorderParameters(aIter, aInlineSegBSize); 6615 if (param.isNothing()) { 6616 return; 6617 } 6618 6619 nsCSSRendering::DrawTableBorderSegment( 6620 aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect, 6621 aIter.mTable->PresContext()->AppUnitsPerDevPixel(), 6622 param->mStartBevelSide, param->mStartBevelOffset, param->mEndBevelSide, 6623 param->mEndBevelOffset); 6624 } 6625 6626 // Pushes a border bevel triangle and substracts the relevant rectangle from 6627 // aRect, which, after all the bevels, will end up being a solid segment rect. 6628 static void AdjustAndPushBevel(wr::DisplayListBuilder& aBuilder, 6629 wr::LayoutRect& aRect, nscolor aColor, 6630 const nsCSSRendering::Bevel& aBevel, 6631 int32_t aAppUnitsPerDevPixel, 6632 bool aBackfaceIsVisible, bool aIsStart) { 6633 if (!aBevel.mOffset) { 6634 return; 6635 } 6636 6637 const auto kTransparent = wr::ToColorF(gfx::DeviceColor(0., 0., 0., 0.)); 6638 const bool horizontal = 6639 aBevel.mSide == eSideTop || aBevel.mSide == eSideBottom; 6640 6641 // Crappy CSS triangle as known by every web developer ever :) 6642 Float offset = NSAppUnitsToFloatPixels(aBevel.mOffset, aAppUnitsPerDevPixel); 6643 wr::LayoutRect bevelRect = aRect; 6644 wr::BorderSide bevelBorder[4]; 6645 for (const auto i : mozilla::AllPhysicalSides()) { 6646 bevelBorder[i] = 6647 wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid); 6648 } 6649 6650 // We're creating a half-transparent triangle using the border primitive. 6651 // 6652 // Classic web-dev trick, with a gotcha: we use a single corner to avoid 6653 // seams and rounding errors. 6654 // 6655 // Classic web-dev trick :P 6656 auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0); 6657 bevelBorder[aBevel.mSide].color = kTransparent; 6658 if (aIsStart) { 6659 if (horizontal) { 6660 bevelBorder[eSideLeft].color = kTransparent; 6661 borderWidths.left = offset; 6662 } else { 6663 bevelBorder[eSideTop].color = kTransparent; 6664 borderWidths.top = offset; 6665 } 6666 } else { 6667 if (horizontal) { 6668 bevelBorder[eSideRight].color = kTransparent; 6669 borderWidths.right = offset; 6670 } else { 6671 bevelBorder[eSideBottom].color = kTransparent; 6672 borderWidths.bottom = offset; 6673 } 6674 } 6675 6676 if (horizontal) { 6677 if (aIsStart) { 6678 aRect.min.x += offset; 6679 aRect.max.x += offset; 6680 } else { 6681 bevelRect.min.x += aRect.width() - offset; 6682 bevelRect.max.x += aRect.width() - offset; 6683 } 6684 aRect.max.x -= offset; 6685 bevelRect.max.y = bevelRect.min.y + aRect.height(); 6686 bevelRect.max.x = bevelRect.min.x + offset; 6687 if (aBevel.mSide == eSideTop) { 6688 borderWidths.bottom = aRect.height(); 6689 } else { 6690 borderWidths.top = aRect.height(); 6691 } 6692 } else { 6693 if (aIsStart) { 6694 aRect.min.y += offset; 6695 aRect.max.y += offset; 6696 } else { 6697 bevelRect.min.y += aRect.height() - offset; 6698 bevelRect.max.y += aRect.height() - offset; 6699 } 6700 aRect.max.y -= offset; 6701 bevelRect.max.x = bevelRect.min.x + aRect.width(); 6702 bevelRect.max.y = bevelRect.min.y + offset; 6703 if (aBevel.mSide == eSideLeft) { 6704 borderWidths.right = aRect.width(); 6705 } else { 6706 borderWidths.left = aRect.width(); 6707 } 6708 } 6709 6710 Range<const wr::BorderSide> wrsides(bevelBorder, 4); 6711 // It's important to _not_ anti-alias the bevel, because otherwise we wouldn't 6712 // be able bevel to sides of the same color without bleeding in the middle. 6713 aBuilder.PushBorder(bevelRect, bevelRect, aBackfaceIsVisible, borderWidths, 6714 wrsides, wr::EmptyBorderRadius(), 6715 wr::AntialiasBorder::No); 6716 } 6717 6718 static void CreateWRCommandsForBeveledBorder( 6719 const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder, 6720 const layers::StackingContextHelper& aSc, const nsPoint& aOffset, 6721 nscoord aAppUnitsPerDevPixel) { 6722 MOZ_ASSERT(aBorderParams.NeedToBevel()); 6723 6724 AutoTArray<nsCSSRendering::SolidBeveledBorderSegment, 3> segments; 6725 nsCSSRendering::GetTableBorderSolidSegments( 6726 segments, aBorderParams.mBorderStyle, aBorderParams.mBorderColor, 6727 aBorderParams.mBorderRect, aAppUnitsPerDevPixel, 6728 aBorderParams.mStartBevelSide, aBorderParams.mStartBevelOffset, 6729 aBorderParams.mEndBevelSide, aBorderParams.mEndBevelOffset); 6730 6731 for (const auto& segment : segments) { 6732 auto rect = LayoutDeviceRect::FromUnknownRect( 6733 NSRectToRect(segment.mRect + aOffset, aAppUnitsPerDevPixel)); 6734 auto r = wr::ToLayoutRect(rect); 6735 auto color = wr::ToColorF(ToDeviceColor(segment.mColor)); 6736 6737 // Adjust for the start bevel if needed. 6738 AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mStartBevel, 6739 aAppUnitsPerDevPixel, aBorderParams.mBackfaceIsVisible, 6740 true); 6741 6742 AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mEndBevel, 6743 aAppUnitsPerDevPixel, aBorderParams.mBackfaceIsVisible, 6744 false); 6745 6746 aBuilder.PushRect(r, r, aBorderParams.mBackfaceIsVisible, false, false, 6747 color); 6748 } 6749 } 6750 6751 static void CreateWRCommandsForBorderSegment( 6752 const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder, 6753 const layers::StackingContextHelper& aSc, const nsPoint& aOffset, 6754 nscoord aAppUnitsPerDevPixel) { 6755 if (aBorderParams.NeedToBevel()) { 6756 CreateWRCommandsForBeveledBorder(aBorderParams, aBuilder, aSc, aOffset, 6757 aAppUnitsPerDevPixel); 6758 return; 6759 } 6760 6761 auto borderRect = LayoutDeviceRect::FromUnknownRect( 6762 NSRectToRect(aBorderParams.mBorderRect + aOffset, aAppUnitsPerDevPixel)); 6763 6764 wr::LayoutRect r = wr::ToLayoutRect(borderRect); 6765 wr::BorderSide wrSide[4]; 6766 for (const auto i : mozilla::AllPhysicalSides()) { 6767 wrSide[i] = wr::ToBorderSide(ToDeviceColor(aBorderParams.mBorderColor), 6768 StyleBorderStyle::None); 6769 } 6770 const bool horizontal = aBorderParams.mStartBevelSide == eSideTop || 6771 aBorderParams.mStartBevelSide == eSideBottom; 6772 auto borderWidth = horizontal ? r.height() : r.width(); 6773 6774 // All border style is set to none except left side. So setting the widths of 6775 // each side to width of rect is fine. 6776 auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0); 6777 6778 wrSide[horizontal ? eSideTop : eSideLeft] = wr::ToBorderSide( 6779 ToDeviceColor(aBorderParams.mBorderColor), aBorderParams.mBorderStyle); 6780 6781 if (horizontal) { 6782 borderWidths.top = borderWidth; 6783 } else { 6784 borderWidths.left = borderWidth; 6785 } 6786 6787 Range<const wr::BorderSide> wrsides(wrSide, 4); 6788 aBuilder.PushBorder(r, r, aBorderParams.mBackfaceIsVisible, borderWidths, 6789 wrsides, wr::EmptyBorderRadius()); 6790 } 6791 6792 void BCBlockDirSeg::CreateWebRenderCommands( 6793 BCPaintBorderIterator& aIter, nscoord aInlineSegBSize, 6794 wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc, 6795 const nsPoint& aOffset) { 6796 Maybe<BCBorderParameters> param = 6797 BuildBorderParameters(aIter, aInlineSegBSize); 6798 if (param.isNothing()) { 6799 return; 6800 } 6801 6802 CreateWRCommandsForBorderSegment( 6803 *param, aBuilder, aSc, aOffset, 6804 aIter.mTable->PresContext()->AppUnitsPerDevPixel()); 6805 } 6806 6807 /** 6808 * Advance the start point of a segment 6809 */ 6810 void BCBlockDirSeg::AdvanceOffsetB() { mOffsetB += mLength - mBEndOffset; } 6811 6812 /** 6813 * Accumulate the current segment 6814 */ 6815 void BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) { 6816 mLastCell = aIter.mCell; 6817 mLength += aIter.mRow->BSize(aIter.mTableWM); 6818 } 6819 6820 BCInlineDirSeg::BCInlineDirSeg() 6821 : mIsIEndBevel(false), 6822 mIEndBevelOffset(0), 6823 mIEndBevelSide(LogicalSide::BStart), 6824 mEndOffset(0), 6825 mOwner(eTableOwner) { 6826 mOffsetI = mOffsetB = mLength = mWidth = mIStartBevelOffset = 0; 6827 mIStartBevelSide = LogicalSide::BStart; 6828 mFirstCell = mAjaCell = nullptr; 6829 } 6830 6831 /** Initialize an inline-dir border segment for painting 6832 * @param aIter - iterator storing the current and adjacent frames 6833 * @param aBorderOwner - which frame owns the border 6834 * @param aBEndBlockSegISize - block-dir segment width coming from up 6835 * @param aInlineSegBSize - the thickness of the segment 6836 + */ 6837 void BCInlineDirSeg::Start(BCPaintBorderIterator& aIter, 6838 BCBorderOwner aBorderOwner, 6839 nscoord aBEndBlockSegISize, 6840 nscoord aInlineSegBSize) { 6841 LogicalSide cornerOwnerSide = LogicalSide::BStart; 6842 bool bevel = false; 6843 6844 mOwner = aBorderOwner; 6845 nscoord cornerSubWidth = 6846 (aIter.mBCData) ? aIter.mBCData->GetCorner(cornerOwnerSide, bevel) : 0; 6847 6848 bool iStartBevel = (aInlineSegBSize > 0) ? bevel : false; 6849 int32_t relColIndex = aIter.GetRelativeColIndex(); 6850 nscoord maxBlockSegISize = 6851 std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aBEndBlockSegISize); 6852 nscoord offset = CalcHorCornerOffset(cornerOwnerSide, cornerSubWidth, 6853 maxBlockSegISize, true, iStartBevel); 6854 mIStartBevelOffset = 6855 (iStartBevel && (aInlineSegBSize > 0)) ? maxBlockSegISize : 0; 6856 // XXX this assumes that only corners where 2 segments join can be beveled 6857 mIStartBevelSide = 6858 (aBEndBlockSegISize > 0) ? LogicalSide::BEnd : LogicalSide::BStart; 6859 mOffsetI += offset; 6860 mLength = -offset; 6861 mWidth = aInlineSegBSize; 6862 mFirstCell = aIter.mCell; 6863 mAjaCell = (aIter.IsDamageAreaBStartMost()) 6864 ? nullptr 6865 : aIter.mBlockDirInfo[relColIndex].mLastCell; 6866 } 6867 6868 /** 6869 * Compute the offsets for the iEnd corner of an inline-dir segment 6870 * @param aIter - iterator containing the structural information 6871 * @param aIStartSegISize - the iSize of the block-dir segment joining the 6872 * corner at the start 6873 */ 6874 void BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator& aIter, 6875 nscoord aIStartSegISize) { 6876 LogicalSide ownerSide = LogicalSide::BStart; 6877 nscoord cornerSubWidth = 0; 6878 bool bevel = false; 6879 if (aIter.mBCData) { 6880 cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); 6881 } 6882 6883 mIsIEndBevel = (mWidth > 0) ? bevel : 0; 6884 int32_t relColIndex = aIter.GetRelativeColIndex(); 6885 nscoord verWidth = 6886 std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aIStartSegISize); 6887 mEndOffset = CalcHorCornerOffset(ownerSide, cornerSubWidth, verWidth, false, 6888 mIsIEndBevel); 6889 mLength += mEndOffset; 6890 mIEndBevelOffset = mIsIEndBevel ? verWidth : 0; 6891 mIEndBevelSide = 6892 (aIStartSegISize > 0) ? LogicalSide::BEnd : LogicalSide::BStart; 6893 } 6894 6895 Maybe<BCBorderParameters> BCInlineDirSeg::BuildBorderParameters( 6896 BCPaintBorderIterator& aIter) { 6897 BCBorderParameters result; 6898 6899 // get the border style, color and paint the segment 6900 LogicalSide side = 6901 aIter.IsDamageAreaBEndMost() ? LogicalSide::BEnd : LogicalSide::BStart; 6902 nsIFrame* rg = aIter.mRg; 6903 if (!rg) ABORT1(Nothing()); 6904 nsIFrame* row = aIter.mRow; 6905 if (!row) ABORT1(Nothing()); 6906 nsIFrame* cell = mFirstCell; 6907 nsIFrame* col; 6908 nsIFrame* owner = nullptr; 6909 result.mBackfaceIsVisible = true; 6910 result.mBorderStyle = StyleBorderStyle::Solid; 6911 result.mBorderColor = 0xFFFFFFFF; 6912 6913 switch (mOwner) { 6914 case eTableOwner: 6915 owner = aIter.mTable; 6916 break; 6917 case eAjaColGroupOwner: 6918 NS_ERROR("neighboring colgroups can never own an inline-dir border"); 6919 [[fallthrough]]; 6920 case eColGroupOwner: 6921 NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(), 6922 "col group can own border only at the table edge"); 6923 col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); 6924 if (!col) ABORT1(Nothing()); 6925 owner = col->GetParent(); 6926 break; 6927 case eAjaColOwner: 6928 NS_ERROR("neighboring column can never own an inline-dir border"); 6929 [[fallthrough]]; 6930 case eColOwner: 6931 NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(), 6932 "col can own border only at the table edge"); 6933 owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); 6934 break; 6935 case eAjaRowGroupOwner: 6936 side = LogicalSide::BEnd; 6937 rg = (aIter.IsTableBEndMost()) ? aIter.mRg : aIter.mPrevRg; 6938 [[fallthrough]]; 6939 case eRowGroupOwner: 6940 owner = rg; 6941 break; 6942 case eAjaRowOwner: 6943 side = LogicalSide::BEnd; 6944 row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow; 6945 [[fallthrough]]; 6946 case eRowOwner: 6947 owner = row; 6948 break; 6949 case eAjaCellOwner: 6950 side = LogicalSide::BEnd; 6951 // if this is null due to the damage area origin-y > 0, then the border 6952 // won't show up anyway 6953 cell = mAjaCell; 6954 [[fallthrough]]; 6955 case eCellOwner: 6956 owner = cell; 6957 break; 6958 } 6959 if (owner) { 6960 ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle, 6961 &result.mBorderColor); 6962 result.mBackfaceIsVisible = !owner->BackfaceIsHidden(); 6963 } 6964 nscoord smallHalf, largeHalf; 6965 DivideBCBorderSize(mWidth, smallHalf, largeHalf); 6966 LogicalRect segRect(aIter.mTableWM, mOffsetI, mOffsetB - largeHalf, mLength, 6967 mWidth); 6968 6969 // Convert logical to physical sides/coordinates for DrawTableBorderSegment. 6970 result.mBorderRect = 6971 segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize()); 6972 result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide); 6973 result.mEndBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide); 6974 result.mStartBevelOffset = mIStartBevelOffset; 6975 result.mEndBevelOffset = mIEndBevelOffset; 6976 // With inline-RTL directionality, the 'start' and 'end' of the inline-dir 6977 // border segment need to be swapped because DrawTableBorderSegment will 6978 // apply the 'start' bevel physically at the left or top edge, and 'end' at 6979 // the right or bottom. 6980 // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal 6981 // writing mode, or "left" or "right" in vertical mode. 6982 // DrawTableBorderSegment works purely with physical coordinates, so it 6983 // expects startBevelOffset to be the indentation-from-the-left or top end 6984 // of the border-segment, and endBevelOffset is the indentation-from-the- 6985 // right or bottom end. If the writing mode is inline-RTL, our "start" and 6986 // "end" will be reversed from this physical-coord view, so we have to swap 6987 // them here. 6988 if (aIter.mTableWM.IsBidiRTL()) { 6989 std::swap(result.mStartBevelSide, result.mEndBevelSide); 6990 std::swap(result.mStartBevelOffset, result.mEndBevelOffset); 6991 } 6992 6993 return Some(result); 6994 } 6995 6996 /** 6997 * Paint the inline-dir segment 6998 * @param aIter - iterator containing the structural information 6999 * @param aDrawTarget - the draw target 7000 */ 7001 void BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter, 7002 DrawTarget& aDrawTarget) { 7003 Maybe<BCBorderParameters> param = BuildBorderParameters(aIter); 7004 if (param.isNothing()) { 7005 return; 7006 } 7007 7008 nsCSSRendering::DrawTableBorderSegment( 7009 aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect, 7010 aIter.mTable->PresContext()->AppUnitsPerDevPixel(), 7011 param->mStartBevelSide, param->mStartBevelOffset, param->mEndBevelSide, 7012 param->mEndBevelOffset); 7013 } 7014 7015 void BCInlineDirSeg::CreateWebRenderCommands( 7016 BCPaintBorderIterator& aIter, wr::DisplayListBuilder& aBuilder, 7017 const layers::StackingContextHelper& aSc, const nsPoint& aPt) { 7018 Maybe<BCBorderParameters> param = BuildBorderParameters(aIter); 7019 if (param.isNothing()) { 7020 return; 7021 } 7022 7023 CreateWRCommandsForBorderSegment( 7024 *param, aBuilder, aSc, aPt, 7025 aIter.mTable->PresContext()->AppUnitsPerDevPixel()); 7026 } 7027 7028 /** 7029 * Advance the start point of a segment 7030 */ 7031 void BCInlineDirSeg::AdvanceOffsetI() { mOffsetI += (mLength - mEndOffset); } 7032 7033 /** 7034 * Accumulate the current segment 7035 */ 7036 void BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) { 7037 mLength += aIter.mBlockDirInfo[aIter.GetRelativeColIndex()].mColWidth; 7038 } 7039 7040 /** 7041 * store the column width information while painting inline-dir segment 7042 */ 7043 void BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) { 7044 if (IsTableIEndMost()) { 7045 mBlockDirInfo[aIndex].mColWidth = mBlockDirInfo[aIndex - 1].mColWidth; 7046 } else { 7047 nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex); 7048 if (!col) ABORT0(); 7049 mBlockDirInfo[aIndex].mColWidth = col->ISize(mTableWM); 7050 } 7051 } 7052 /** 7053 * Determine if a block-dir segment owns the corner 7054 */ 7055 bool BCPaintBorderIterator::BlockDirSegmentOwnsCorner() { 7056 LogicalSide cornerOwnerSide = LogicalSide::BStart; 7057 bool bevel = false; 7058 if (mBCData) { 7059 mBCData->GetCorner(cornerOwnerSide, bevel); 7060 } 7061 // unitialized ownerside, bevel 7062 return (LogicalSide::BStart == cornerOwnerSide) || 7063 (LogicalSide::BEnd == cornerOwnerSide); 7064 } 7065 7066 /** 7067 * Paint if necessary an inline-dir segment, otherwise accumulate it 7068 * @param aDrawTarget - the draw target 7069 */ 7070 void BCPaintBorderIterator::AccumulateOrDoActionInlineDirSegment( 7071 BCPaintBorderAction& aAction) { 7072 int32_t relColIndex = GetRelativeColIndex(); 7073 // store the current col width if it hasn't been already 7074 if (mBlockDirInfo[relColIndex].mColWidth < 0) { 7075 StoreColumnWidth(relColIndex); 7076 } 7077 7078 BCBorderOwner borderOwner = eCellOwner; 7079 BCBorderOwner ignoreBorderOwner; 7080 bool isSegStart = true; 7081 bool ignoreSegStart; 7082 7083 nscoord iStartSegISize = 7084 mBCData ? mBCData->GetIStartEdge(ignoreBorderOwner, ignoreSegStart) : 0; 7085 nscoord bStartSegBSize = 7086 mBCData ? mBCData->GetBStartEdge(borderOwner, isSegStart) : 0; 7087 7088 if (mIsNewRow || (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) { 7089 // reset for every new row and on the bottom of the last row 7090 mInlineSeg.mOffsetB = mNextOffsetB; 7091 mNextOffsetB = mNextOffsetB + mRow->BSize(mTableWM); 7092 mInlineSeg.mOffsetI = mInitialOffsetI; 7093 mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize); 7094 } 7095 7096 if (!IsDamageAreaIStartMost() && 7097 (isSegStart || IsDamageAreaIEndMost() || BlockDirSegmentOwnsCorner())) { 7098 // paint the previous seg or the current one if IsDamageAreaIEndMost() 7099 if (mInlineSeg.mLength > 0) { 7100 mInlineSeg.GetIEndCorner(*this, iStartSegISize); 7101 if (mInlineSeg.mWidth > 0) { 7102 if (aAction.mMode == BCPaintBorderAction::Mode::Paint) { 7103 mInlineSeg.Paint(*this, aAction.mPaintData.mDrawTarget); 7104 } else { 7105 MOZ_ASSERT(aAction.mMode == 7106 BCPaintBorderAction::Mode::CreateWebRenderCommands); 7107 mInlineSeg.CreateWebRenderCommands( 7108 *this, aAction.mCreateWebRenderCommandsData.mBuilder, 7109 aAction.mCreateWebRenderCommandsData.mSc, 7110 aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame); 7111 } 7112 } 7113 mInlineSeg.AdvanceOffsetI(); 7114 } 7115 mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize); 7116 } 7117 mInlineSeg.IncludeCurrentBorder(*this); 7118 mBlockDirInfo[relColIndex].mWidth = iStartSegISize; 7119 mBlockDirInfo[relColIndex].mLastCell = mCell; 7120 } 7121 7122 /** 7123 * Paint if necessary a block-dir segment, otherwise accumulate it 7124 * @param aDrawTarget - the draw target 7125 */ 7126 void BCPaintBorderIterator::AccumulateOrDoActionBlockDirSegment( 7127 BCPaintBorderAction& aAction) { 7128 BCBorderOwner borderOwner = eCellOwner; 7129 BCBorderOwner ignoreBorderOwner; 7130 bool isSegStart = true; 7131 bool ignoreSegStart; 7132 7133 nscoord blockSegISize = 7134 mBCData ? mBCData->GetIStartEdge(borderOwner, isSegStart) : 0; 7135 nscoord inlineSegBSize = 7136 mBCData ? mBCData->GetBStartEdge(ignoreBorderOwner, ignoreSegStart) : 0; 7137 7138 int32_t relColIndex = GetRelativeColIndex(); 7139 BCBlockDirSeg& blockDirSeg = mBlockDirInfo[relColIndex]; 7140 if (!blockDirSeg.mCol) { // on the first damaged row and the first segment in 7141 // the col 7142 blockDirSeg.Initialize(*this); 7143 blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize, 7144 Nothing{}); 7145 } 7146 7147 if (!IsDamageAreaBStartMost() && 7148 (isSegStart || IsDamageAreaBEndMost() || IsAfterRepeatedHeader() || 7149 StartRepeatedFooter())) { 7150 Maybe<nscoord> emptyRowEndSize; 7151 // paint the previous seg or the current one if IsDamageAreaBEndMost() 7152 if (blockDirSeg.mLength > 0) { 7153 blockDirSeg.GetBEndCorner(*this, inlineSegBSize); 7154 if (blockDirSeg.mWidth > 0) { 7155 if (aAction.mMode == BCPaintBorderAction::Mode::Paint) { 7156 blockDirSeg.Paint(*this, aAction.mPaintData.mDrawTarget, 7157 inlineSegBSize); 7158 } else { 7159 MOZ_ASSERT(aAction.mMode == 7160 BCPaintBorderAction::Mode::CreateWebRenderCommands); 7161 blockDirSeg.CreateWebRenderCommands( 7162 *this, inlineSegBSize, 7163 aAction.mCreateWebRenderCommandsData.mBuilder, 7164 aAction.mCreateWebRenderCommandsData.mSc, 7165 aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame); 7166 } 7167 } 7168 blockDirSeg.AdvanceOffsetB(); 7169 if (mRow->PrincipalChildList().IsEmpty()) { 7170 emptyRowEndSize = Some(mRow->BSize(mTableWM)); 7171 } 7172 } 7173 blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize, 7174 emptyRowEndSize); 7175 } 7176 blockDirSeg.IncludeCurrentBorder(*this); 7177 mPrevInlineSegBSize = inlineSegBSize; 7178 } 7179 7180 /** 7181 * Reset the block-dir information cache 7182 */ 7183 void BCPaintBorderIterator::ResetVerInfo() { 7184 if (mBlockDirInfo) { 7185 memset(mBlockDirInfo.get(), 0, 7186 mDamageArea.ColCount() * sizeof(BCBlockDirSeg)); 7187 // XXX reinitialize properly 7188 for (auto xIndex : IntegerRange(mDamageArea.ColCount())) { 7189 mBlockDirInfo[xIndex].mColWidth = -1; 7190 } 7191 } 7192 } 7193 7194 void nsTableFrame::IterateBCBorders(BCPaintBorderAction& aAction, 7195 const nsRect& aDirtyRect) { 7196 // We first transfer the aDirtyRect into cellmap coordinates to compute which 7197 // cell borders need to be painted 7198 BCPaintBorderIterator iter(this); 7199 if (!iter.SetDamageArea(aDirtyRect)) { 7200 return; 7201 } 7202 7203 // XXX comment still has physical terminology 7204 // First, paint all of the vertical borders from top to bottom and left to 7205 // right as they become complete. They are painted first, since they are less 7206 // efficient to paint than horizontal segments. They were stored with as few 7207 // segments as possible (since horizontal borders are painted last and 7208 // possibly over them). For every cell in a row that fails in the damage are 7209 // we look up if the current border would start a new segment, if so we paint 7210 // the previously stored vertical segment and start a new segment. After 7211 // this we the now active segment with the current border. These 7212 // segments are stored in mBlockDirInfo to be used on the next row 7213 for (iter.First(); !iter.mAtEnd; iter.Next()) { 7214 iter.AccumulateOrDoActionBlockDirSegment(aAction); 7215 } 7216 7217 // Next, paint all of the inline-dir border segments from bStart to bEnd reuse 7218 // the mBlockDirInfo array to keep track of col widths and block-dir segments 7219 // for corner calculations 7220 iter.Reset(); 7221 for (iter.First(); !iter.mAtEnd; iter.Next()) { 7222 iter.AccumulateOrDoActionInlineDirSegment(aAction); 7223 } 7224 } 7225 7226 /** 7227 * Method to paint BCBorders, this does not use currently display lists although 7228 * it will do this in future 7229 * @param aDrawTarget - the rendering context 7230 * @param aDirtyRect - inside this rectangle the BC Borders will redrawn 7231 */ 7232 void nsTableFrame::PaintBCBorders(DrawTarget& aDrawTarget, 7233 const nsRect& aDirtyRect) { 7234 BCPaintBorderAction action(aDrawTarget); 7235 IterateBCBorders(action, aDirtyRect); 7236 } 7237 7238 void nsTableFrame::CreateWebRenderCommandsForBCBorders( 7239 wr::DisplayListBuilder& aBuilder, 7240 const mozilla::layers::StackingContextHelper& aSc, 7241 const nsRect& aVisibleRect, const nsPoint& aOffsetToReferenceFrame) { 7242 BCPaintBorderAction action(aBuilder, aSc, aOffsetToReferenceFrame); 7243 // We always draw whole table border for webrender. Passing the visible rect 7244 // dirty rect. 7245 IterateBCBorders(action, aVisibleRect - aOffsetToReferenceFrame); 7246 } 7247 7248 bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) { 7249 bool result = false; 7250 nsTableCellMap* cellMap = GetCellMap(); 7251 MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated."); 7252 if (cellMap) { 7253 result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols); 7254 } 7255 return result; 7256 } 7257 7258 bool nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) { 7259 bool result = false; 7260 nsTableCellMap* cellMap = GetCellMap(); 7261 MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated."); 7262 if (cellMap) { 7263 result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols); 7264 } 7265 return result; 7266 } 7267 7268 /* static */ 7269 void nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame, 7270 const nsRect& aOrigRect, 7271 const nsRect& aOrigInkOverflow, 7272 bool aIsFirstReflow) { 7273 nsIFrame* parent = aFrame->GetParent(); 7274 NS_ASSERTION(parent, "What happened here?"); 7275 7276 if (parent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 7277 // Don't bother; we'll invalidate the parent's overflow rect when 7278 // we finish reflowing it. 7279 return; 7280 } 7281 7282 // The part that looks at both the rect and the overflow rect is a 7283 // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent 7284 // description of its hackishness. 7285 // 7286 // This doesn't really make sense now that we have DLBI. 7287 // This code can probably be simplified a fair bit. 7288 nsRect inkOverflow = aFrame->InkOverflowRect(); 7289 if (aIsFirstReflow || aOrigRect.TopLeft() != aFrame->GetPosition() || 7290 aOrigInkOverflow.TopLeft() != inkOverflow.TopLeft()) { 7291 // Invalidate the old and new overflow rects. Note that if the 7292 // frame moved, we can't just use aOrigInkOverflow, since it's in 7293 // coordinates relative to the old position. So invalidate via 7294 // aFrame's parent, and reposition that overflow rect to the right 7295 // place. 7296 // XXXbz this doesn't handle outlines, does it? 7297 aFrame->InvalidateFrame(); 7298 parent->InvalidateFrameWithRect(aOrigInkOverflow + aOrigRect.TopLeft()); 7299 } else if (aOrigRect.Size() != aFrame->GetSize() || 7300 aOrigInkOverflow.Size() != inkOverflow.Size()) { 7301 aFrame->InvalidateFrameWithRect(aOrigInkOverflow); 7302 aFrame->InvalidateFrame(); 7303 } 7304 } 7305 7306 void nsTableFrame::AppendDirectlyOwnedAnonBoxes( 7307 nsTArray<OwnedAnonBox>& aResult) { 7308 nsIFrame* wrapper = GetParent(); 7309 MOZ_ASSERT(wrapper->Style()->GetPseudoType() == PseudoStyleType::tableWrapper, 7310 "What happened to our parent?"); 7311 aResult.AppendElement( 7312 OwnedAnonBox(wrapper, &UpdateStyleOfOwnedAnonBoxesForTableWrapper)); 7313 } 7314 7315 /* static */ 7316 void nsTableFrame::UpdateStyleOfOwnedAnonBoxesForTableWrapper( 7317 nsIFrame* aOwningFrame, nsIFrame* aWrapperFrame, 7318 ServoRestyleState& aRestyleState) { 7319 MOZ_ASSERT( 7320 aWrapperFrame->Style()->GetPseudoType() == PseudoStyleType::tableWrapper, 7321 "What happened to our parent?"); 7322 7323 RefPtr<ComputedStyle> newStyle = 7324 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle( 7325 PseudoStyleType::tableWrapper, aOwningFrame->Style()); 7326 7327 // Figure out whether we have an actual change. It's important that we do 7328 // this, even though all the wrapper's changes are due to properties it 7329 // inherits from us, because it's possible that no one ever asked us for those 7330 // style structs and hence changes to them aren't reflected in 7331 // the handled changes at all. 7332 // 7333 // Also note that extensions can add/remove stylesheets that change the styles 7334 // of anonymous boxes directly, so we need to handle that potential change 7335 // here. 7336 // 7337 // NOTE(emilio): We can't use the ChangesHandledFor optimization (and we 7338 // assert against that), because the table wrapper is up in the frame tree 7339 // compared to the owner frame. 7340 uint32_t equalStructs; // Not used, actually. 7341 nsChangeHint wrapperHint = 7342 aWrapperFrame->Style()->CalcStyleDifference(*newStyle, &equalStructs); 7343 7344 if (wrapperHint) { 7345 aRestyleState.ChangeList().AppendChange( 7346 aWrapperFrame, aWrapperFrame->GetContent(), wrapperHint); 7347 } 7348 7349 for (nsIFrame* cur = aWrapperFrame; cur; cur = cur->GetNextContinuation()) { 7350 cur->SetComputedStyle(newStyle); 7351 } 7352 7353 MOZ_ASSERT(!aWrapperFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES), 7354 "Wrapper frame doesn't have any anon boxes of its own!"); 7355 } 7356 7357 namespace mozilla { 7358 7359 nsRect nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, 7360 bool* aSnap) const { 7361 *aSnap = false; 7362 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); 7363 } 7364 7365 nsDisplayTableBackgroundSet::nsDisplayTableBackgroundSet( 7366 nsDisplayListBuilder* aBuilder, nsIFrame* aTable) 7367 : mBuilder(aBuilder), 7368 mColGroupBackgrounds(aBuilder), 7369 mColBackgrounds(aBuilder) { 7370 mPrevTableBackgroundSet = mBuilder->SetTableBackgroundSet(this); 7371 mozilla::DebugOnly<const nsIFrame*> reference = 7372 mBuilder->FindReferenceFrameFor(aTable, &mToReferenceFrame); 7373 MOZ_ASSERT(nsLayoutUtils::FindNearestCommonAncestorFrame(reference, aTable)); 7374 mDirtyRect = mBuilder->GetDirtyRect(); 7375 mCombinedTableClipChain = 7376 mBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); 7377 mTableASR = mBuilder->CurrentActiveScrolledRoot(); 7378 } 7379 7380 // A display item that draws all collapsed borders for a table. 7381 // At some point, we may want to find a nicer partitioning for dividing 7382 // border-collapse segments into their own display items. 7383 class nsDisplayTableBorderCollapse final : public nsDisplayTableItem { 7384 public: 7385 nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder, 7386 nsTableFrame* aFrame) 7387 : nsDisplayTableItem(aBuilder, aFrame) { 7388 MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse); 7389 } 7390 7391 MOZ_COUNTED_DTOR_FINAL(nsDisplayTableBorderCollapse) 7392 7393 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; 7394 bool CreateWebRenderCommands( 7395 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 7396 const StackingContextHelper& aSc, 7397 layers::RenderRootStateManager* aManager, 7398 nsDisplayListBuilder* aDisplayListBuilder) override; 7399 NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE) 7400 }; 7401 7402 void nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder, 7403 gfxContext* aCtx) { 7404 nsPoint pt = ToReferenceFrame(); 7405 DrawTarget* drawTarget = aCtx->GetDrawTarget(); 7406 7407 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( 7408 pt, mFrame->PresContext()->AppUnitsPerDevPixel()); 7409 7410 // XXX we should probably get rid of this translation at some stage 7411 // But that would mean modifying PaintBCBorders, ugh 7412 AutoRestoreTransform autoRestoreTransform(drawTarget); 7413 drawTarget->SetTransform( 7414 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); 7415 7416 static_cast<nsTableFrame*>(mFrame)->PaintBCBorders( 7417 *drawTarget, GetPaintRect(aBuilder, aCtx) - pt); 7418 } 7419 7420 bool nsDisplayTableBorderCollapse::CreateWebRenderCommands( 7421 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 7422 const StackingContextHelper& aSc, 7423 mozilla::layers::RenderRootStateManager* aManager, 7424 nsDisplayListBuilder* aDisplayListBuilder) { 7425 bool dummy; 7426 static_cast<nsTableFrame*>(mFrame)->CreateWebRenderCommandsForBCBorders( 7427 aBuilder, aSc, GetBounds(aDisplayListBuilder, &dummy), 7428 ToReferenceFrame()); 7429 return true; 7430 } 7431 7432 } // namespace mozilla