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