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