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