no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / layout / tables / nsTableFrame.cpp
blobe7fd7340bf0189828b03d3e951d5d4216b2f7cc9
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 = eLogicalSideBStart;
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 eLogicalSideBStart, 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 = 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 = IsInline(aSide) ? eLogicalSideBStart : eLogicalSideIStart;
4590 subElem = eTableOwner;
4591 subStyle = StyleBorderStyle::Solid;
4594 // Add a new border going in the direction of a given side, and update the
4595 // dominant border.
4596 void BCCornerInfo::Update(mozilla::LogicalSide aSide, BCCellBorder aBorder) {
4597 if (ownerStyle == BORDER_STYLE_UNSET) {
4598 Set(aSide, aBorder);
4599 } else {
4600 bool isInline = IsInline(aSide); // relative to the corner
4601 BCCellBorder oldBorder, tempBorder;
4602 oldBorder.owner = (BCBorderOwner)ownerElem;
4603 oldBorder.style = ownerStyle;
4604 oldBorder.width = ownerWidth;
4605 oldBorder.color = ownerColor;
4607 LogicalSide oldSide = LogicalSide(ownerSide);
4609 bool existingWins = false;
4610 tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline,
4611 &existingWins);
4613 ownerElem = tempBorder.owner;
4614 ownerStyle = tempBorder.style;
4615 ownerWidth = tempBorder.width;
4616 ownerColor = tempBorder.color;
4617 if (existingWins) { // existing corner is dominant
4618 if (::Perpendicular(LogicalSide(ownerSide), aSide)) {
4619 // see if the new sub info replaces the old
4620 BCCellBorder subBorder;
4621 subBorder.owner = (BCBorderOwner)subElem;
4622 subBorder.style = subStyle;
4623 subBorder.width = subWidth;
4624 subBorder.color = 0; // we are not interested in subBorder color
4625 bool firstWins;
4627 tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline,
4628 &firstWins);
4630 subElem = tempBorder.owner;
4631 subStyle = tempBorder.style;
4632 subWidth = tempBorder.width;
4633 if (!firstWins) {
4634 subSide = aSide;
4637 } else { // input args are dominant
4638 ownerSide = aSide;
4639 if (::Perpendicular(oldSide, LogicalSide(ownerSide))) {
4640 subElem = oldBorder.owner;
4641 subStyle = oldBorder.style;
4642 subWidth = oldBorder.width;
4643 subSide = oldSide;
4646 if (aBorder.width > 0) {
4647 numSegs++;
4648 if (!hasDashDot && ((StyleBorderStyle::Dashed == aBorder.style) ||
4649 (StyleBorderStyle::Dotted == aBorder.style))) {
4650 hasDashDot = 1;
4654 // bevel the corner if only two perpendicular non dashed/dotted segments
4655 // enter the corner
4656 bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot);
4660 struct BCCorners {
4661 BCCorners(int32_t aNumCorners, int32_t aStartIndex);
4663 BCCornerInfo& operator[](int32_t i) const {
4664 NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
4665 return corners[clamped(i, startIndex, endIndex) - startIndex];
4668 int32_t startIndex;
4669 int32_t endIndex;
4670 UniquePtr<BCCornerInfo[]> corners;
4673 BCCorners::BCCorners(int32_t aNumCorners, int32_t aStartIndex) {
4674 NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error");
4675 startIndex = aStartIndex;
4676 endIndex = aStartIndex + aNumCorners - 1;
4677 corners = MakeUnique<BCCornerInfo[]>(aNumCorners);
4680 struct BCCellBorders {
4681 BCCellBorders(int32_t aNumBorders, int32_t aStartIndex);
4683 BCCellBorder& operator[](int32_t i) const {
4684 NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
4685 return borders[clamped(i, startIndex, endIndex) - startIndex];
4688 int32_t startIndex;
4689 int32_t endIndex;
4690 UniquePtr<BCCellBorder[]> borders;
4693 BCCellBorders::BCCellBorders(int32_t aNumBorders, int32_t aStartIndex) {
4694 NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error");
4695 startIndex = aStartIndex;
4696 endIndex = aStartIndex + aNumBorders - 1;
4697 borders = MakeUnique<BCCellBorder[]>(aNumBorders);
4700 // this function sets the new border properties and returns true if the border
4701 // segment will start a new segment and not be accumulated into the previous
4702 // segment.
4703 static bool SetBorder(const BCCellBorder& aNewBorder, BCCellBorder& aBorder) {
4704 bool changed = (aNewBorder.style != aBorder.style) ||
4705 (aNewBorder.width != aBorder.width) ||
4706 (aNewBorder.color != aBorder.color);
4707 aBorder.color = aNewBorder.color;
4708 aBorder.width = aNewBorder.width;
4709 aBorder.style = aNewBorder.style;
4710 aBorder.owner = aNewBorder.owner;
4712 return changed;
4715 // this function will set the inline-dir border. It will return true if the
4716 // existing segment will not be continued. Having a block-dir owner of a corner
4717 // should also start a new segment.
4718 static bool SetInlineDirBorder(const BCCellBorder& aNewBorder,
4719 const BCCornerInfo& aCorner,
4720 BCCellBorder& aBorder) {
4721 bool startSeg = ::SetBorder(aNewBorder, aBorder);
4722 if (!startSeg) {
4723 startSeg = !IsInline(LogicalSide(aCorner.ownerSide));
4725 return startSeg;
4728 // Make the damage area larger on the top and bottom by at least one row and on
4729 // the left and right at least one column. This is done so that adjacent
4730 // elements are part of the border calculations. The extra segments and borders
4731 // outside the actual damage area will not be updated in the cell map, because
4732 // they in turn would need info from adjacent segments outside the damage area
4733 // to be accurate.
4734 void nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const {
4735 int32_t numRows = GetRowCount();
4736 int32_t numCols = GetColCount();
4738 int32_t dStartX = aArea.StartCol();
4739 int32_t dEndX = aArea.EndCol() - 1;
4740 int32_t dStartY = aArea.StartRow();
4741 int32_t dEndY = aArea.EndRow() - 1;
4743 // expand the damage area in each direction
4744 if (dStartX > 0) {
4745 dStartX--;
4747 if (dEndX < (numCols - 1)) {
4748 dEndX++;
4750 if (dStartY > 0) {
4751 dStartY--;
4753 if (dEndY < (numRows - 1)) {
4754 dEndY++;
4756 // Check the damage area so that there are no cells spanning in or out. If
4757 // there are any then make the damage area as big as the table, similarly to
4758 // the way the cell map decides whether to rebuild versus expand. This could
4759 // be optimized to expand to the smallest area that contains no spanners, but
4760 // it may not be worth the effort in general, and it would need to be done in
4761 // the cell map as well.
4762 bool haveSpanner = false;
4763 if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) ||
4764 (dEndY < (numRows - 1))) {
4765 nsTableCellMap* tableCellMap = GetCellMap();
4766 if (!tableCellMap) ABORT0();
4767 // Get the ordered row groups
4768 RowGroupArray rowGroups = OrderedRowGroups();
4770 // Scope outside loop to be used as hint.
4771 nsCellMap* cellMap = nullptr;
4772 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
4773 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
4774 int32_t rgStartY = rgFrame->GetStartRowIndex();
4775 int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1;
4776 if (dEndY < rgStartY) break;
4777 cellMap = tableCellMap->GetMapFor(rgFrame, cellMap);
4778 if (!cellMap) ABORT0();
4779 // check for spanners from above and below
4780 if ((dStartY > 0) && (dStartY >= rgStartY) && (dStartY <= rgEndY)) {
4781 if (uint32_t(dStartY - rgStartY) >= cellMap->mRows.Length()) ABORT0();
4782 const nsCellMap::CellDataArray& row =
4783 cellMap->mRows[dStartY - rgStartY];
4784 for (int32_t x = dStartX; x <= dEndX; x++) {
4785 CellData* cellData = row.SafeElementAt(x);
4786 if (cellData && (cellData->IsRowSpan())) {
4787 haveSpanner = true;
4788 break;
4791 if (dEndY < rgEndY) {
4792 if (uint32_t(dEndY + 1 - rgStartY) >= cellMap->mRows.Length())
4793 ABORT0();
4794 const nsCellMap::CellDataArray& row2 =
4795 cellMap->mRows[dEndY + 1 - rgStartY];
4796 for (int32_t x = dStartX; x <= dEndX; x++) {
4797 CellData* cellData = row2.SafeElementAt(x);
4798 if (cellData && (cellData->IsRowSpan())) {
4799 haveSpanner = true;
4800 break;
4805 // check for spanners on the left and right
4806 int32_t iterStartY;
4807 int32_t iterEndY;
4808 if ((dStartY >= rgStartY) && (dStartY <= rgEndY)) {
4809 // the damage area starts in the row group
4810 iterStartY = dStartY;
4811 iterEndY = std::min(dEndY, rgEndY);
4812 } else if ((dEndY >= rgStartY) && (dEndY <= rgEndY)) {
4813 // the damage area ends in the row group
4814 iterStartY = rgStartY;
4815 iterEndY = dEndY;
4816 } else if ((rgStartY >= dStartY) && (rgEndY <= dEndY)) {
4817 // the damage area contains the row group
4818 iterStartY = rgStartY;
4819 iterEndY = rgEndY;
4820 } else {
4821 // the damage area does not overlap the row group
4822 continue;
4824 NS_ASSERTION(iterStartY >= 0 && iterEndY >= 0,
4825 "table index values are expected to be nonnegative");
4826 for (int32_t y = iterStartY; y <= iterEndY; y++) {
4827 if (uint32_t(y - rgStartY) >= cellMap->mRows.Length()) ABORT0();
4828 const nsCellMap::CellDataArray& row = cellMap->mRows[y - rgStartY];
4829 CellData* cellData = row.SafeElementAt(dStartX);
4830 if (cellData && (cellData->IsColSpan())) {
4831 haveSpanner = true;
4832 break;
4834 if (dEndX < (numCols - 1)) {
4835 cellData = row.SafeElementAt(dEndX + 1);
4836 if (cellData && (cellData->IsColSpan())) {
4837 haveSpanner = true;
4838 break;
4844 if (haveSpanner) {
4845 // make the damage area the whole table
4846 aArea.StartCol() = 0;
4847 aArea.StartRow() = 0;
4848 aArea.ColCount() = numCols;
4849 aArea.RowCount() = numRows;
4850 } else {
4851 aArea.StartCol() = dStartX;
4852 aArea.StartRow() = dStartY;
4853 aArea.ColCount() = 1 + dEndX - dStartX;
4854 aArea.RowCount() = 1 + dEndY - dStartY;
4858 #define ADJACENT true
4859 #define INLINE_DIR true
4861 void BCMapCellInfo::SetTableBStartBorderWidth(BCPixelSize aWidth) {
4862 mTableBCData->mBStartBorderWidth =
4863 std::max(mTableBCData->mBStartBorderWidth, aWidth);
4866 void BCMapCellInfo::SetTableIStartBorderWidth(int32_t aRowB,
4867 BCPixelSize aWidth) {
4868 // update the iStart first cell border
4869 if (aRowB == 0) {
4870 mTableBCData->mIStartCellBorderWidth = aWidth;
4872 mTableBCData->mIStartBorderWidth =
4873 std::max(mTableBCData->mIStartBorderWidth, aWidth);
4876 void BCMapCellInfo::SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth) {
4877 // update the iEnd first cell border
4878 if (aRowB == 0) {
4879 mTableBCData->mIEndCellBorderWidth = aWidth;
4881 mTableBCData->mIEndBorderWidth =
4882 std::max(mTableBCData->mIEndBorderWidth, aWidth);
4885 void BCMapCellInfo::SetIEndBorderWidths(BCPixelSize aWidth) {
4886 // update the borders of the cells and cols affected
4887 if (mCell) {
4888 mCell->SetBorderWidth(
4889 eLogicalSideIEnd,
4890 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideIEnd)));
4892 if (mEndCol) {
4893 BCPixelSize half = BC_BORDER_START_HALF(aWidth);
4894 mEndCol->SetIEndBorderWidth(std::max(half, mEndCol->GetIEndBorderWidth()));
4898 void BCMapCellInfo::SetBEndBorderWidths(BCPixelSize aWidth) {
4899 // update the borders of the affected cells and rows
4900 if (mCell) {
4901 mCell->SetBorderWidth(
4902 eLogicalSideBEnd,
4903 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideBEnd)));
4905 if (mEndRow) {
4906 BCPixelSize half = BC_BORDER_START_HALF(aWidth);
4907 mEndRow->SetBEndBCBorderWidth(
4908 std::max(half, mEndRow->GetBEndBCBorderWidth()));
4912 void BCMapCellInfo::SetBStartBorderWidths(BCPixelSize aWidth) {
4913 if (mCell) {
4914 mCell->SetBorderWidth(
4915 eLogicalSideBStart,
4916 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideBStart)));
4918 if (mStartRow) {
4919 BCPixelSize half = BC_BORDER_END_HALF(aWidth);
4920 mStartRow->SetBStartBCBorderWidth(
4921 std::max(half, mStartRow->GetBStartBCBorderWidth()));
4925 void BCMapCellInfo::SetIStartBorderWidths(BCPixelSize aWidth) {
4926 if (mCell) {
4927 mCell->SetBorderWidth(
4928 eLogicalSideIStart,
4929 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideIStart)));
4931 if (mStartCol) {
4932 BCPixelSize half = BC_BORDER_END_HALF(aWidth);
4933 mStartCol->SetIStartBorderWidth(
4934 std::max(half, mStartCol->GetIStartBorderWidth()));
4938 void BCMapCellInfo::SetTableBEndBorderWidth(BCPixelSize aWidth) {
4939 mTableBCData->mBEndBorderWidth =
4940 std::max(mTableBCData->mBEndBorderWidth, aWidth);
4943 void BCMapCellInfo::SetColumn(int32_t aColX) {
4944 mCurrentColFrame = mTableFirstInFlow->GetColFrame(aColX);
4945 mCurrentColGroupFrame =
4946 static_cast<nsTableColGroupFrame*>(mCurrentColFrame->GetParent());
4947 if (!mCurrentColGroupFrame) {
4948 NS_ERROR("null mCurrentColGroupFrame");
4952 void BCMapCellInfo::IncrementRow(bool aResetToBStartRowOfCell) {
4953 mCurrentRowFrame =
4954 aResetToBStartRowOfCell ? mStartRow : mCurrentRowFrame->GetNextRow();
4957 BCCellBorder BCMapCellInfo::GetBStartEdgeBorder() {
4958 return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame,
4959 mRowGroup, mStartRow, mCell, mTableWM,
4960 eLogicalSideBStart, !ADJACENT);
4963 BCCellBorder BCMapCellInfo::GetBEndEdgeBorder() {
4964 return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame,
4965 mRowGroup, mEndRow, mCell, mTableWM, eLogicalSideBEnd,
4966 ADJACENT);
4968 BCCellBorder BCMapCellInfo::GetIStartEdgeBorder() {
4969 return CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup,
4970 mCurrentRowFrame, mCell, mTableWM, eLogicalSideIStart,
4971 !ADJACENT);
4973 BCCellBorder BCMapCellInfo::GetIEndEdgeBorder() {
4974 return CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
4975 mCurrentRowFrame, mCell, mTableWM, eLogicalSideIEnd,
4976 ADJACENT);
4978 BCCellBorder BCMapCellInfo::GetIEndInternalBorder() {
4979 const nsIFrame* cg = mCgAtEnd ? mColGroup : nullptr;
4980 return CompareBorders(nullptr, cg, mEndCol, nullptr, nullptr, mCell, mTableWM,
4981 eLogicalSideIEnd, ADJACENT);
4984 BCCellBorder BCMapCellInfo::GetIStartInternalBorder() {
4985 const nsIFrame* cg = mCgAtStart ? mColGroup : nullptr;
4986 return CompareBorders(nullptr, cg, mStartCol, nullptr, nullptr, mCell,
4987 mTableWM, eLogicalSideIStart, !ADJACENT);
4990 BCCellBorder BCMapCellInfo::GetBEndInternalBorder() {
4991 const nsIFrame* rg = mRgAtEnd ? mRowGroup : nullptr;
4992 return CompareBorders(nullptr, nullptr, nullptr, rg, mEndRow, mCell, mTableWM,
4993 eLogicalSideBEnd, ADJACENT);
4996 BCCellBorder BCMapCellInfo::GetBStartInternalBorder() {
4997 const nsIFrame* rg = mRgAtStart ? mRowGroup : nullptr;
4998 return CompareBorders(nullptr, nullptr, nullptr, rg, mStartRow, mCell,
4999 mTableWM, eLogicalSideBStart, !ADJACENT);
5002 // Calculate border information for border-collapsed tables.
5003 // Because borders of table/row/cell, etc merge into one, we need to
5004 // determine which border dominates at each cell. In addition, corner-specific
5005 // information, e.g. bevelling, is computed as well.
5007 // Here is the order for storing border edges in the cell map as a cell is
5008 // processed.
5010 // For each cell, at least 4 edges are processed:
5011 // * There are colspan * N block-start and block-end edges.
5012 // * There are rowspan * N inline-start and inline-end edges.
5014 // 1) If the cell being processed is at the block-start of the table, store the
5015 // block-start edge.
5016 // 2) If the cell being processed is at the inline-start of the table, store
5017 // the
5018 // inline-start edge.
5019 // 3) Store the inline-end edge.
5020 // 4) Store the block-end edge.
5022 // These steps are traced by calls to `SetBCBorderEdge`.
5024 // Corners are indexed by columns only, to avoid allocating a full row * col
5025 // array of `BCCornerInfo`. This trades off memory allocation versus moving
5026 // previous corner information around.
5028 // For each cell:
5029 // 1) If the cell is at the block-start of the table, but not at the
5030 // inline-start of the table, store its block-start inline-start corner.
5032 // 2) If the cell is at the inline-start of the table, store the block-start
5033 // inline-start corner.
5035 // 3) If the cell is at the block-start inline-end of the table, or not at the
5036 // block-start of the table, store the block-start inline-end corner.
5038 // 4) If the cell is at the block-end inline-end of the table, store the
5039 // block-end inline-end corner.
5041 // 5) If the cell is at the block-end of the table, store the block-end
5042 // inline-start.
5044 // Visually, it looks like this:
5046 // 2--1--1--1--1--1--3
5047 // | | | | | | |
5048 // 2--3--3--3--3--3--3
5049 // | | | | | | |
5050 // 2--3--3--3--3--3--3
5051 // | | | | | | |
5052 // 5--5--5--5--5--5--4
5054 // For rowspan/colspan cells, the latest border information is propagated
5055 // along its "corners".
5057 // These steps are traced by calls to `SetBCBorderCorner`.
5058 void nsTableFrame::CalcBCBorders() {
5059 NS_ASSERTION(IsBorderCollapse(),
5060 "calling CalcBCBorders on separated-border table");
5061 nsTableCellMap* tableCellMap = GetCellMap();
5062 if (!tableCellMap) ABORT0();
5063 int32_t numRows = GetRowCount();
5064 int32_t numCols = GetColCount();
5065 if (!numRows || !numCols) return; // nothing to do
5067 // Get the property holding the table damage area and border widths
5068 TableBCData* propData = GetTableBCData();
5069 if (!propData) ABORT0();
5071 TableArea damageArea(propData->mDamageArea);
5072 // See documentation for why we do this.
5073 ExpandBCDamageArea(damageArea);
5075 // We accumulate border widths as we process the cells, so we need
5076 // to reset it once in the beginning.
5077 bool tableBorderReset[4];
5078 for (uint32_t sideX = 0; sideX < ArrayLength(tableBorderReset); sideX++) {
5079 tableBorderReset[sideX] = false;
5082 // Storage for block-direction borders from the previous row, indexed by
5083 // columns.
5084 BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1,
5085 damageArea.StartCol());
5086 if (!lastBlockDirBorders.borders) ABORT0();
5087 if (damageArea.StartRow() != 0) {
5088 // Ok, we've filled with information about the previous row's borders with
5089 // the default state, which is "no borders." This is incorrect, and leaving
5090 // it will result in an erroneous behaviour if the previous row did have
5091 // borders, and the dirty rows don't, as we will not mark the beginning of
5092 // the no border segment.
5093 TableArea prevRowArea(damageArea.StartCol(), damageArea.StartRow() - 1,
5094 damageArea.ColCount(), 1);
5095 BCMapCellIterator iter(this, prevRowArea);
5096 BCMapCellInfo info(this);
5097 for (iter.First(info); !iter.mAtEnd; iter.Next(info)) {
5098 if (info.mColIndex == prevRowArea.StartCol()) {
5099 lastBlockDirBorders.borders[0] = info.GetIStartEdgeBorder();
5101 lastBlockDirBorders.borders[info.mColIndex - prevRowArea.StartCol() + 1] =
5102 info.GetIEndEdgeBorder();
5105 // Inline direction border at block start of the table, computed by the
5106 // previous cell. Unused afterwards.
5107 Maybe<BCCellBorder> firstRowBStartEdgeBorder;
5108 BCCellBorder lastBEndBorder;
5109 // Storage for inline-direction borders from previous cells, indexed by
5110 // columns.
5111 // TODO(dshin): Why ColCount + 1? Number of inline segments should match
5112 // column count exactly, unlike block direction segments...
5113 BCCellBorders lastBEndBorders(damageArea.ColCount() + 1,
5114 damageArea.StartCol());
5115 if (!lastBEndBorders.borders) ABORT0();
5117 BCMapCellInfo info(this);
5119 // Block-start corners of the cell being traversed, indexed by columns.
5120 BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol());
5121 if (!bStartCorners.corners) ABORT0();
5122 // Block-end corners of the cell being traversed, indexed by columns.
5123 // Note that when a new row starts, they become block-start corners and used
5124 // as such, until cleared with `Set`.
5125 BCCorners bEndCorners(damageArea.ColCount() + 1, damageArea.StartCol());
5126 if (!bEndCorners.corners) ABORT0();
5128 BCMapCellIterator iter(this, damageArea);
5129 for (iter.First(info); !iter.mAtEnd; iter.Next(info)) {
5130 // see if firstRowBStartEdgeBorder, lastBEndBorder need to be reset
5131 if (iter.IsNewRow()) {
5132 if (info.mRowIndex == 0) {
5133 BCCellBorder border;
5134 if (info.mColIndex == 0) {
5135 border.Reset(info.mRowIndex, info.mRowSpan);
5136 } else {
5137 // Similar to lastBlockDirBorders, the previous block-start border
5138 // is filled by actually quering the adjacent cell.
5139 BCMapCellInfo ajaInfo(this);
5140 iter.PeekIStart(info, info.mRowIndex, ajaInfo);
5141 border = ajaInfo.GetBStartEdgeBorder();
5143 firstRowBStartEdgeBorder = Some(border);
5144 } else {
5145 firstRowBStartEdgeBorder = Nothing{};
5147 if (info.mColIndex == 0) {
5148 lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
5149 } else {
5150 // Same as above, but for block-end border.
5151 BCMapCellInfo ajaInfo(this);
5152 iter.PeekIStart(info, info.mRowIndex, ajaInfo);
5153 lastBEndBorder = ajaInfo.GetBEndEdgeBorder();
5155 } else if (info.mColIndex > damageArea.StartCol()) {
5156 lastBEndBorder = lastBEndBorders[info.mColIndex - 1];
5157 if (lastBEndBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) {
5158 // the bEnd border's iStart edge butts against the middle of a rowspan
5159 lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
5163 // find the dominant border considering the cell's bStart border and the
5164 // table, row group, row if the border is at the bStart of the table,
5165 // otherwise it was processed in a previous row
5166 if (0 == info.mRowIndex) {
5167 if (!tableBorderReset[eLogicalSideBStart]) {
5168 propData->mBStartBorderWidth = 0;
5169 tableBorderReset[eLogicalSideBStart] = true;
5171 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
5172 colIdx++) {
5173 info.SetColumn(colIdx);
5174 BCCellBorder currentBorder = info.GetBStartEdgeBorder();
5175 BCCornerInfo& bStartIStartCorner = bStartCorners[colIdx];
5176 // Mark inline-end direction border from this corner.
5177 if (0 == colIdx) {
5178 bStartIStartCorner.Set(eLogicalSideIEnd, currentBorder);
5179 } else {
5180 bStartIStartCorner.Update(eLogicalSideIEnd, currentBorder);
5181 tableCellMap->SetBCBorderCorner(
5182 LogicalCorner::BStartIStart, *iter.mCellMap, 0, 0, colIdx,
5183 LogicalSide(bStartIStartCorner.ownerSide),
5184 bStartIStartCorner.subWidth, bStartIStartCorner.bevel);
5186 // Above, we set the corner `colIndex` column as having a border towards
5187 // inline-end, heading towards the next column. Vice versa is also true,
5188 // where the next column has a border heading towards this column.
5189 bStartCorners[colIdx + 1].Set(eLogicalSideIStart, currentBorder);
5190 MOZ_ASSERT(firstRowBStartEdgeBorder,
5191 "Inline start border tracking not set?");
5192 // update firstRowBStartEdgeBorder and see if a new segment starts
5193 bool startSeg =
5194 firstRowBStartEdgeBorder
5195 ? SetInlineDirBorder(currentBorder, bStartIStartCorner,
5196 firstRowBStartEdgeBorder.ref())
5197 : true;
5198 // store the border segment in the cell map
5199 tableCellMap->SetBCBorderEdge(eLogicalSideBStart, *iter.mCellMap, 0, 0,
5200 colIdx, 1, currentBorder.owner,
5201 currentBorder.width, startSeg);
5203 // Set border width at block-start (table-wide and for the cell), but
5204 // only if it's the largest we've encountered.
5205 info.SetTableBStartBorderWidth(currentBorder.width);
5206 info.SetBStartBorderWidths(currentBorder.width);
5208 } else {
5209 // see if the bStart border needs to be the start of a segment due to a
5210 // block-dir border owning the corner
5211 if (info.mColIndex > 0) {
5212 BCData& data = info.mCellData->mData;
5213 if (!data.IsBStartStart()) {
5214 LogicalSide cornerSide;
5215 bool bevel;
5216 data.GetCorner(cornerSide, bevel);
5217 if (IsBlock(cornerSide)) {
5218 data.SetBStartStart(true);
5224 // find the dominant border considering the cell's iStart border and the
5225 // table, col group, col if the border is at the iStart of the table,
5226 // otherwise it was processed in a previous col
5227 if (0 == info.mColIndex) {
5228 if (!tableBorderReset[eLogicalSideIStart]) {
5229 propData->mIStartBorderWidth = 0;
5230 tableBorderReset[eLogicalSideIStart] = true;
5232 info.mCurrentRowFrame = nullptr;
5233 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
5234 rowB++) {
5235 info.IncrementRow(rowB == info.mRowIndex);
5236 BCCellBorder currentBorder = info.GetIStartEdgeBorder();
5237 BCCornerInfo& bStartIStartCorner =
5238 (0 == rowB) ? bStartCorners[0] : bEndCorners[0];
5239 bStartIStartCorner.Update(eLogicalSideBEnd, currentBorder);
5240 tableCellMap->SetBCBorderCorner(
5241 LogicalCorner::BStartIStart, *iter.mCellMap, iter.mRowGroupStart,
5242 rowB, 0, LogicalSide(bStartIStartCorner.ownerSide),
5243 bStartIStartCorner.subWidth, bStartIStartCorner.bevel);
5244 bEndCorners[0].Set(eLogicalSideBStart, currentBorder);
5246 // update lastBlockDirBorders and see if a new segment starts
5247 bool startSeg = SetBorder(currentBorder, lastBlockDirBorders[0]);
5248 // store the border segment in the cell map
5249 tableCellMap->SetBCBorderEdge(eLogicalSideIStart, *iter.mCellMap,
5250 iter.mRowGroupStart, rowB, info.mColIndex,
5251 1, currentBorder.owner,
5252 currentBorder.width, startSeg);
5253 // Set border width at inline-start (table-wide and for the cell), but
5254 // only if it's the largest we've encountered.
5255 info.SetTableIStartBorderWidth(rowB, currentBorder.width);
5256 info.SetIStartBorderWidths(currentBorder.width);
5260 // find the dominant border considering the cell's iEnd border, adjacent
5261 // cells and the table, row group, row
5262 if (info.mNumTableCols == info.GetCellEndColIndex() + 1) {
5263 // touches iEnd edge of table
5264 if (!tableBorderReset[eLogicalSideIEnd]) {
5265 propData->mIEndBorderWidth = 0;
5266 tableBorderReset[eLogicalSideIEnd] = true;
5268 info.mCurrentRowFrame = nullptr;
5269 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
5270 rowB++) {
5271 info.IncrementRow(rowB == info.mRowIndex);
5272 BCCellBorder currentBorder = info.GetIEndEdgeBorder();
5273 // Update/store the bStart-iEnd & bEnd-iEnd corners. Note that we
5274 // overwrite all corner information to the end of the column span.
5275 BCCornerInfo& bStartIEndCorner =
5276 (0 == rowB) ? bStartCorners[info.GetCellEndColIndex() + 1]
5277 : bEndCorners[info.GetCellEndColIndex() + 1];
5278 bStartIEndCorner.Update(eLogicalSideBEnd, currentBorder);
5279 tableCellMap->SetBCBorderCorner(
5280 LogicalCorner::BStartIEnd, *iter.mCellMap, iter.mRowGroupStart,
5281 rowB, info.GetCellEndColIndex(),
5282 LogicalSide(bStartIEndCorner.ownerSide), bStartIEndCorner.subWidth,
5283 bStartIEndCorner.bevel);
5284 BCCornerInfo& bEndIEndCorner =
5285 bEndCorners[info.GetCellEndColIndex() + 1];
5286 bEndIEndCorner.Set(eLogicalSideBStart, currentBorder);
5287 tableCellMap->SetBCBorderCorner(
5288 LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5289 info.GetCellEndColIndex(), LogicalSide(bEndIEndCorner.ownerSide),
5290 bEndIEndCorner.subWidth, bEndIEndCorner.bevel);
5291 // update lastBlockDirBorders and see if a new segment starts
5292 bool startSeg = SetBorder(
5293 currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
5294 // store the border segment in the cell map and update cellBorders
5295 tableCellMap->SetBCBorderEdge(
5296 eLogicalSideIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5297 info.GetCellEndColIndex(), 1, currentBorder.owner,
5298 currentBorder.width, startSeg);
5299 // Set border width at inline-end (table-wide and for the cell), but
5300 // only if it's the largest we've encountered.
5301 info.SetTableIEndBorderWidth(rowB, currentBorder.width);
5302 info.SetIEndBorderWidths(currentBorder.width);
5304 } else {
5305 // Cell entries, but not on the block-end side of the entire table.
5306 int32_t segLength = 0;
5307 BCMapCellInfo ajaInfo(this);
5308 BCMapCellInfo priorAjaInfo(this);
5309 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
5310 rowB += segLength) {
5311 // Grab the cell adjacent to our inline-end.
5312 iter.PeekIEnd(info, rowB, ajaInfo);
5313 BCCellBorder currentBorder = info.GetIEndInternalBorder();
5314 BCCellBorder adjacentBorder = ajaInfo.GetIStartInternalBorder();
5315 currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
5316 adjacentBorder, !INLINE_DIR);
5318 segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowB);
5319 segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowB);
5321 // update lastBlockDirBorders and see if a new segment starts
5322 bool startSeg = SetBorder(
5323 currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
5324 // store the border segment in the cell map and update cellBorders
5325 if (info.GetCellEndColIndex() < damageArea.EndCol() &&
5326 rowB >= damageArea.StartRow() && rowB < damageArea.EndRow()) {
5327 tableCellMap->SetBCBorderEdge(
5328 eLogicalSideIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5329 info.GetCellEndColIndex(), segLength, currentBorder.owner,
5330 currentBorder.width, startSeg);
5331 info.SetIEndBorderWidths(currentBorder.width);
5332 ajaInfo.SetIStartBorderWidths(currentBorder.width);
5334 // Does the block-start inline-end corner hit the inline-end adjacent
5335 // cell that wouldn't have an inline border? e.g.
5337 // o-----------o---------------o
5338 // | | |
5339 // o-----------x Adjacent cell o
5340 // | This Cell | (rowspan) |
5341 // o-----------o---------------o
5342 bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) &&
5343 (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
5344 BCCornerInfo* bStartIEndCorner =
5345 ((0 == rowB) || hitsSpanOnIEnd)
5346 ? &bStartCorners[info.GetCellEndColIndex() + 1]
5347 : &bEndCorners[info.GetCellEndColIndex() +
5348 1]; // From previous row.
5349 bStartIEndCorner->Update(eLogicalSideBEnd, currentBorder);
5350 // If this is a rowspan, need to consider if this "corner" is generating
5351 // an inline segment for the adjacent cell. e.g.
5353 // o--------------o----o
5354 // | | |
5355 // o x----o
5356 // | (This "row") | |
5357 // o--------------o----o
5358 if (rowB != info.mRowIndex) {
5359 currentBorder = priorAjaInfo.GetBEndInternalBorder();
5360 BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder();
5361 currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
5362 adjacentBorder, INLINE_DIR);
5363 bStartIEndCorner->Update(eLogicalSideIEnd, currentBorder);
5365 // Check that the spanned area is inside of the invalidation area
5366 if (info.GetCellEndColIndex() < damageArea.EndCol() &&
5367 rowB >= damageArea.StartRow()) {
5368 if (0 != rowB) {
5369 // Ok, actually store the information
5370 tableCellMap->SetBCBorderCorner(
5371 LogicalCorner::BStartIEnd, *iter.mCellMap, iter.mRowGroupStart,
5372 rowB, info.GetCellEndColIndex(),
5373 LogicalSide(bStartIEndCorner->ownerSide),
5374 bStartIEndCorner->subWidth, bStartIEndCorner->bevel);
5376 // Propagate this segment down the rowspan
5377 for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) {
5378 tableCellMap->SetBCBorderCorner(
5379 LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart,
5380 rX, info.GetCellEndColIndex(),
5381 LogicalSide(bStartIEndCorner->ownerSide),
5382 bStartIEndCorner->subWidth, false);
5385 hitsSpanOnIEnd =
5386 (rowB + segLength < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
5387 BCCornerInfo& bEndIEndCorner =
5388 (hitsSpanOnIEnd) ? bStartCorners[info.GetCellEndColIndex() + 1]
5389 : bEndCorners[info.GetCellEndColIndex() + 1];
5390 bEndIEndCorner.Set(eLogicalSideBStart, currentBorder);
5391 priorAjaInfo = ajaInfo;
5394 for (int32_t colIdx = info.mColIndex + 1;
5395 colIdx <= info.GetCellEndColIndex(); colIdx++) {
5396 lastBlockDirBorders[colIdx].Reset(0, 1);
5399 // find the dominant border considering the cell's bEnd border, adjacent
5400 // cells and the table, row group, row
5401 if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) {
5402 // touches bEnd edge of table
5403 if (!tableBorderReset[eLogicalSideBEnd]) {
5404 propData->mBEndBorderWidth = 0;
5405 tableBorderReset[eLogicalSideBEnd] = true;
5407 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
5408 colIdx++) {
5409 info.SetColumn(colIdx);
5410 BCCellBorder currentBorder = info.GetBEndEdgeBorder();
5411 BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx];
5412 bEndIStartCorner.Update(eLogicalSideIEnd, currentBorder);
5413 tableCellMap->SetBCBorderCorner(
5414 LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart,
5415 info.GetCellEndRowIndex(), colIdx,
5416 LogicalSide(bEndIStartCorner.ownerSide), bEndIStartCorner.subWidth,
5417 bEndIStartCorner.bevel);
5418 BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + 1];
5419 bEndIEndCorner.Update(eLogicalSideIStart, currentBorder);
5420 // Store the block-end inline-end corner if it also is the block-end
5421 // inline-end of the overall table.
5422 if (info.mNumTableCols == colIdx + 1) {
5423 tableCellMap->SetBCBorderCorner(
5424 LogicalCorner::BEndIEnd, *iter.mCellMap, iter.mRowGroupStart,
5425 info.GetCellEndRowIndex(), colIdx,
5426 LogicalSide(bEndIEndCorner.ownerSide), bEndIEndCorner.subWidth,
5427 bEndIEndCorner.bevel, true);
5429 // update lastBEndBorder and see if a new segment starts
5430 bool startSeg =
5431 SetInlineDirBorder(currentBorder, bEndIStartCorner, lastBEndBorder);
5432 if (!startSeg) {
5433 // make sure that we did not compare apples to oranges i.e. the
5434 // current border should be a continuation of the lastBEndBorder,
5435 // as it is a bEnd border
5436 // add 1 to the info.GetCellEndRowIndex()
5437 startSeg =
5438 (lastBEndBorder.rowIndex != (info.GetCellEndRowIndex() + 1));
5440 // store the border segment in the cell map and update cellBorders
5441 tableCellMap->SetBCBorderEdge(
5442 eLogicalSideBEnd, *iter.mCellMap, iter.mRowGroupStart,
5443 info.GetCellEndRowIndex(), colIdx, 1, currentBorder.owner,
5444 currentBorder.width, startSeg);
5445 // update lastBEndBorders
5446 lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
5447 lastBEndBorder.rowSpan = info.mRowSpan;
5448 lastBEndBorders[colIdx] = lastBEndBorder;
5450 // Set border width at block-end (table-wide and for the cell), but
5451 // only if it's the largest we've encountered.
5452 info.SetBEndBorderWidths(currentBorder.width);
5453 info.SetTableBEndBorderWidth(currentBorder.width);
5455 } else {
5456 int32_t segLength = 0;
5457 BCMapCellInfo ajaInfo(this);
5458 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
5459 colIdx += segLength) {
5460 // Grab the cell adjacent to our block-end.
5461 iter.PeekBEnd(info, colIdx, ajaInfo);
5462 BCCellBorder currentBorder = info.GetBEndInternalBorder();
5463 BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder();
5464 currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
5465 adjacentBorder, INLINE_DIR);
5466 segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colIdx);
5467 segLength =
5468 std::min(segLength, info.mColIndex + info.mColSpan - colIdx);
5470 BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx];
5471 // e.g.
5472 // o--o----------o
5473 // | | This col |
5474 // o--x----------o
5475 // | Adjacent |
5476 // o--o----------o
5477 bool hitsSpanBelow = (colIdx > ajaInfo.mColIndex) &&
5478 (colIdx < ajaInfo.mColIndex + ajaInfo.mColSpan);
5479 bool update = true;
5480 if (colIdx == info.mColIndex && colIdx > damageArea.StartCol()) {
5481 int32_t prevRowIndex = lastBEndBorders[colIdx - 1].rowIndex;
5482 if (prevRowIndex > info.GetCellEndRowIndex() + 1) {
5483 // hits a rowspan on the iEnd side
5484 update = false;
5485 // the corner was taken care of during the cell on the iStart side
5486 } else if (prevRowIndex < info.GetCellEndRowIndex() + 1) {
5487 // spans below the cell to the iStart side
5488 bStartCorners[colIdx] = bEndIStartCorner;
5489 bEndIStartCorner.Set(eLogicalSideIEnd, currentBorder);
5490 update = false;
5493 if (update) {
5494 bEndIStartCorner.Update(eLogicalSideIEnd, currentBorder);
5496 // Check that the spanned area is inside of the invalidation area
5497 if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
5498 colIdx >= damageArea.StartCol()) {
5499 if (hitsSpanBelow) {
5500 tableCellMap->SetBCBorderCorner(
5501 LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart,
5502 info.GetCellEndRowIndex(), colIdx,
5503 LogicalSide(bEndIStartCorner.ownerSide),
5504 bEndIStartCorner.subWidth, bEndIStartCorner.bevel);
5506 // Propagate this segment down the colspan
5507 for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) {
5508 BCCornerInfo& corner = bEndCorners[c];
5509 corner.Set(eLogicalSideIEnd, currentBorder);
5510 tableCellMap->SetBCBorderCorner(
5511 LogicalCorner::BEndIStart, *iter.mCellMap, iter.mRowGroupStart,
5512 info.GetCellEndRowIndex(), c, LogicalSide(corner.ownerSide),
5513 corner.subWidth, false);
5516 // update lastBEndBorders and see if a new segment starts
5517 bool startSeg =
5518 SetInlineDirBorder(currentBorder, bEndIStartCorner, lastBEndBorder);
5519 if (!startSeg) {
5520 // make sure that we did not compare apples to oranges i.e. the
5521 // current border should be a continuation of the lastBEndBorder,
5522 // as it is a bEnd border
5523 // add 1 to the info.GetCellEndRowIndex()
5524 startSeg = (lastBEndBorder.rowIndex != info.GetCellEndRowIndex() + 1);
5526 lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
5527 lastBEndBorder.rowSpan = info.mRowSpan;
5528 for (int32_t c = colIdx; c < colIdx + segLength; c++) {
5529 lastBEndBorders[c] = lastBEndBorder;
5532 // store the border segment the cell map and update cellBorders
5533 if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
5534 colIdx >= damageArea.StartCol() && colIdx < damageArea.EndCol()) {
5535 tableCellMap->SetBCBorderEdge(
5536 eLogicalSideBEnd, *iter.mCellMap, iter.mRowGroupStart,
5537 info.GetCellEndRowIndex(), colIdx, segLength, currentBorder.owner,
5538 currentBorder.width, startSeg);
5539 info.SetBEndBorderWidths(currentBorder.width);
5540 ajaInfo.SetBStartBorderWidths(currentBorder.width);
5542 // update bEnd-iEnd corner
5543 BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + segLength];
5544 bEndIEndCorner.Update(eLogicalSideIStart, currentBorder);
5547 // o------o------o
5548 // | c1 | |
5549 // o------o c2 o
5550 // | c3 | |
5551 // o--e1--o--e2--o
5552 // We normally join edges of successive block-end inline segments by
5553 // consulting the previous segment; however, cell c2's block-end inline
5554 // segment e2 is processed before e1, so we need to process such joins
5555 // out-of-band here, when we're processing c3.
5556 const auto nextColIndex = info.GetCellEndColIndex() + 1;
5557 if ((info.mNumTableCols != nextColIndex) &&
5558 (lastBEndBorders[nextColIndex].rowSpan > 1) &&
5559 (lastBEndBorders[nextColIndex].rowIndex ==
5560 info.GetCellEndRowIndex() + 1)) {
5561 BCCornerInfo& corner = bEndCorners[nextColIndex];
5562 if (!IsBlock(LogicalSide(corner.ownerSide))) {
5563 // not a block-dir owner
5564 BCCellBorder& thisBorder = lastBEndBorder;
5565 BCCellBorder& nextBorder = lastBEndBorders[info.mColIndex + 1];
5566 if ((thisBorder.color == nextBorder.color) &&
5567 (thisBorder.width == nextBorder.width) &&
5568 (thisBorder.style == nextBorder.style)) {
5569 // set the flag on the next border indicating it is not the start of a
5570 // new segment
5571 if (iter.mCellMap) {
5572 tableCellMap->ResetBStartStart(
5573 eLogicalSideBEnd, *iter.mCellMap, iter.mRowGroupStart,
5574 info.GetCellEndRowIndex(), nextColIndex);
5579 } // for (iter.First(info); info.mCell; iter.Next(info)) {
5580 // reset the bc flag and damage area
5581 SetNeedToCalcBCBorders(false);
5582 propData->mDamageArea = TableArea(0, 0, 0, 0);
5583 #ifdef DEBUG_TABLE_CELLMAP
5584 mCellMap->Dump();
5585 #endif
5588 class BCPaintBorderIterator;
5590 struct BCBorderParameters {
5591 StyleBorderStyle mBorderStyle;
5592 nscolor mBorderColor;
5593 nsRect mBorderRect;
5594 int32_t mAppUnitsPerDevPixel;
5595 mozilla::Side mStartBevelSide;
5596 nscoord mStartBevelOffset;
5597 mozilla::Side mEndBevelSide;
5598 nscoord mEndBevelOffset;
5599 bool mBackfaceIsVisible;
5601 bool NeedToBevel() const {
5602 if (!mStartBevelOffset && !mEndBevelOffset) {
5603 return false;
5606 if (mBorderStyle == StyleBorderStyle::Dashed ||
5607 mBorderStyle == StyleBorderStyle::Dotted) {
5608 return false;
5611 return true;
5615 struct BCBlockDirSeg {
5616 BCBlockDirSeg();
5618 void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner,
5619 BCPixelSize aBlockSegISize, BCPixelSize aInlineSegBSize,
5620 Maybe<nscoord> aEmptyRowEndSize);
5622 void Initialize(BCPaintBorderIterator& aIter);
5623 void GetBEndCorner(BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize);
5625 Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter,
5626 BCPixelSize aInlineSegBSize);
5627 void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget,
5628 BCPixelSize aInlineSegBSize);
5629 void CreateWebRenderCommands(BCPaintBorderIterator& aIter,
5630 BCPixelSize aInlineSegBSize,
5631 wr::DisplayListBuilder& aBuilder,
5632 const layers::StackingContextHelper& aSc,
5633 const nsPoint& aPt);
5634 void AdvanceOffsetB();
5635 void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
5637 union {
5638 nsTableColFrame* mCol;
5639 int32_t mColWidth;
5641 nscoord mOffsetI; // i-offset with respect to the table edge
5642 nscoord mOffsetB; // b-offset with respect to the table edge
5643 nscoord mLength; // block-dir length including corners
5644 BCPixelSize mWidth; // thickness in pixels
5646 nsTableCellFrame* mAjaCell; // previous sibling to the first cell
5647 // where the segment starts, it can be
5648 // the owner of a segment
5649 nsTableCellFrame* mFirstCell; // cell at the start of the segment
5650 nsTableRowGroupFrame*
5651 mFirstRowGroup; // row group at the start of the segment
5652 nsTableRowFrame* mFirstRow; // row at the start of the segment
5653 nsTableCellFrame* mLastCell; // cell at the current end of the
5654 // segment
5656 uint8_t mOwner; // owner of the border, defines the
5657 // style
5658 LogicalSide mBStartBevelSide; // direction to bevel at the bStart
5659 nscoord mBStartBevelOffset; // how much to bevel at the bStart
5660 BCPixelSize mBEndInlineSegBSize; // bSize of the crossing
5661 // inline-dir border
5662 nscoord mBEndOffset; // how much longer is the segment due
5663 // to the inline-dir border, by this
5664 // amount the next segment needs to be
5665 // shifted.
5666 bool mIsBEndBevel; // should we bevel at the bEnd
5669 struct BCInlineDirSeg {
5670 BCInlineDirSeg();
5672 void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner,
5673 BCPixelSize aBEndBlockSegISize, BCPixelSize aInlineSegBSize);
5674 void GetIEndCorner(BCPaintBorderIterator& aIter, BCPixelSize aIStartSegISize);
5675 void AdvanceOffsetI();
5676 void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
5677 Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter);
5678 void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget);
5679 void CreateWebRenderCommands(BCPaintBorderIterator& aIter,
5680 wr::DisplayListBuilder& aBuilder,
5681 const layers::StackingContextHelper& aSc,
5682 const nsPoint& aPt);
5684 nscoord mOffsetI; // i-offset with respect to the table edge
5685 nscoord mOffsetB; // b-offset with respect to the table edge
5686 nscoord mLength; // inline-dir length including corners
5687 BCPixelSize mWidth; // border thickness in pixels
5688 nscoord mIStartBevelOffset; // how much to bevel at the iStart
5689 LogicalSide mIStartBevelSide; // direction to bevel at the iStart
5690 bool mIsIEndBevel; // should we bevel at the iEnd end
5691 nscoord mIEndBevelOffset; // how much to bevel at the iEnd
5692 LogicalSide mIEndBevelSide; // direction to bevel at the iEnd
5693 nscoord mEndOffset; // how much longer is the segment due
5694 // to the block-dir border, by this
5695 // amount the next segment needs to be
5696 // shifted.
5697 uint8_t mOwner; // owner of the border, defines the
5698 // style
5699 nsTableCellFrame* mFirstCell; // cell at the start of the segment
5700 nsTableCellFrame* mAjaCell; // neighboring cell to the first cell
5701 // where the segment starts, it can be
5702 // the owner of a segment
5705 struct BCPaintData {
5706 explicit BCPaintData(DrawTarget& aDrawTarget) : mDrawTarget(aDrawTarget) {}
5708 DrawTarget& mDrawTarget;
5711 struct BCCreateWebRenderCommandsData {
5712 BCCreateWebRenderCommandsData(wr::DisplayListBuilder& aBuilder,
5713 const layers::StackingContextHelper& aSc,
5714 const nsPoint& aOffsetToReferenceFrame)
5715 : mBuilder(aBuilder),
5716 mSc(aSc),
5717 mOffsetToReferenceFrame(aOffsetToReferenceFrame) {}
5719 wr::DisplayListBuilder& mBuilder;
5720 const layers::StackingContextHelper& mSc;
5721 const nsPoint& mOffsetToReferenceFrame;
5724 struct BCPaintBorderAction {
5725 explicit BCPaintBorderAction(DrawTarget& aDrawTarget)
5726 : mMode(Mode::Paint), mPaintData(aDrawTarget) {}
5728 BCPaintBorderAction(wr::DisplayListBuilder& aBuilder,
5729 const layers::StackingContextHelper& aSc,
5730 const nsPoint& aOffsetToReferenceFrame)
5731 : mMode(Mode::CreateWebRenderCommands),
5732 mCreateWebRenderCommandsData(aBuilder, aSc, aOffsetToReferenceFrame) {}
5734 ~BCPaintBorderAction() {
5735 // mCreateWebRenderCommandsData is in a union which means the destructor
5736 // wouldn't be called when BCPaintBorderAction get destroyed. So call the
5737 // destructor here explicitly.
5738 if (mMode == Mode::CreateWebRenderCommands) {
5739 mCreateWebRenderCommandsData.~BCCreateWebRenderCommandsData();
5743 enum class Mode {
5744 Paint,
5745 CreateWebRenderCommands,
5748 Mode mMode;
5750 union {
5751 BCPaintData mPaintData;
5752 BCCreateWebRenderCommandsData mCreateWebRenderCommandsData;
5756 // Iterates over borders (iStart border, corner, bStart border) in the cell map
5757 // within a damage area from iStart to iEnd, bStart to bEnd. All members are in
5758 // terms of the 1st in flow frames, except where suffixed by InFlow.
5759 class BCPaintBorderIterator {
5760 public:
5761 explicit BCPaintBorderIterator(nsTableFrame* aTable);
5762 void Reset();
5765 * Determine the damage area in terms of rows and columns and finalize
5766 * mInitialOffsetI and mInitialOffsetB.
5767 * @param aDirtyRect - dirty rect in table coordinates
5768 * @return - true if we need to paint something given dirty rect
5770 bool SetDamageArea(const nsRect& aDamageRect);
5771 void First();
5772 void Next();
5773 void AccumulateOrDoActionInlineDirSegment(BCPaintBorderAction& aAction);
5774 void AccumulateOrDoActionBlockDirSegment(BCPaintBorderAction& aAction);
5775 void ResetVerInfo();
5776 void StoreColumnWidth(int32_t aIndex);
5777 bool BlockDirSegmentOwnsCorner();
5779 nsTableFrame* mTable;
5780 nsTableFrame* mTableFirstInFlow;
5781 nsTableCellMap* mTableCellMap;
5782 nsCellMap* mCellMap;
5783 WritingMode mTableWM;
5784 nsTableFrame::RowGroupArray mRowGroups;
5786 nsTableRowGroupFrame* mPrevRg;
5787 nsTableRowGroupFrame* mRg;
5788 bool mIsRepeatedHeader;
5789 bool mIsRepeatedFooter;
5790 nsTableRowGroupFrame* mStartRg; // first row group in the damagearea
5791 int32_t mRgIndex; // current row group index in the
5792 // mRowgroups array
5793 int32_t mFifRgFirstRowIndex; // start row index of the first in
5794 // flow of the row group
5795 int32_t mRgFirstRowIndex; // row index of the first row in the
5796 // row group
5797 int32_t mRgLastRowIndex; // row index of the last row in the row
5798 // group
5799 int32_t mNumTableRows; // number of rows in the table and all
5800 // continuations
5801 int32_t mNumTableCols; // number of columns in the table
5802 int32_t mColIndex; // with respect to the table
5803 int32_t mRowIndex; // with respect to the table
5804 int32_t mRepeatedHeaderRowIndex; // row index in a repeated
5805 // header, it's equivalent to
5806 // mRowIndex when we're in a repeated
5807 // header, and set to the last row
5808 // index of a repeated header when
5809 // we're not
5810 bool mIsNewRow;
5811 bool mAtEnd; // the iterator cycled over all
5812 // borders
5813 nsTableRowFrame* mPrevRow;
5814 nsTableRowFrame* mRow;
5815 nsTableRowFrame* mStartRow; // first row in a inside the damagearea
5817 // cell properties
5818 nsTableCellFrame* mPrevCell;
5819 nsTableCellFrame* mCell;
5820 BCCellData* mPrevCellData;
5821 BCCellData* mCellData;
5822 BCData* mBCData;
5824 bool IsTableBStartMost() {
5825 return (mRowIndex == 0) && !mTable->GetPrevInFlow();
5827 bool IsTableIEndMost() { return (mColIndex >= mNumTableCols); }
5828 bool IsTableBEndMost() {
5829 return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();
5831 bool IsTableIStartMost() { return (mColIndex == 0); }
5832 bool IsDamageAreaBStartMost() const {
5833 return mRowIndex == mDamageArea.StartRow();
5835 bool IsDamageAreaIEndMost() const {
5836 return mColIndex >= mDamageArea.EndCol();
5838 bool IsDamageAreaBEndMost() const {
5839 return mRowIndex >= mDamageArea.EndRow();
5841 bool IsDamageAreaIStartMost() const {
5842 return mColIndex == mDamageArea.StartCol();
5844 int32_t GetRelativeColIndex() const {
5845 return mColIndex - mDamageArea.StartCol();
5848 TableArea mDamageArea; // damageArea in cellmap coordinates
5849 bool IsAfterRepeatedHeader() {
5850 return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1));
5852 bool StartRepeatedFooter() const {
5853 return mIsRepeatedFooter && mRowIndex == mRgFirstRowIndex &&
5854 mRowIndex != mDamageArea.StartRow();
5857 nscoord mInitialOffsetI; // offsetI of the first border with
5858 // respect to the table
5859 nscoord mInitialOffsetB; // offsetB of the first border with
5860 // respect to the table
5861 nscoord mNextOffsetB; // offsetB of the next segment
5862 // this array is used differently when
5863 // inline-dir and block-dir borders are drawn
5864 // When inline-dir border are drawn we cache
5865 // the column widths and the width of the
5866 // block-dir borders that arrive from bStart
5867 // When we draw block-dir borders we store
5868 // lengths and width for block-dir borders
5869 // before they are drawn while we move over
5870 // the columns in the damage area
5871 // It has one more elements than columns are
5872 // in the table.
5873 UniquePtr<BCBlockDirSeg[]> mBlockDirInfo;
5874 BCInlineDirSeg mInlineSeg; // the inline-dir segment while we
5875 // move over the colums
5876 BCPixelSize mPrevInlineSegBSize; // the bSize of the previous
5877 // inline-dir border
5879 private:
5880 bool SetNewRow(nsTableRowFrame* aRow = nullptr);
5881 bool SetNewRowGroup();
5882 void SetNewData(int32_t aRowIndex, int32_t aColIndex);
5885 BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable)
5886 : mTable(aTable),
5887 mTableFirstInFlow(static_cast<nsTableFrame*>(aTable->FirstInFlow())),
5888 mTableCellMap(aTable->GetCellMap()),
5889 mCellMap(nullptr),
5890 mTableWM(aTable->Style()),
5891 mRowGroups(aTable->OrderedRowGroups()),
5892 mPrevRg(nullptr),
5893 mRg(nullptr),
5894 mIsRepeatedHeader(false),
5895 mIsRepeatedFooter(false),
5896 mStartRg(nullptr),
5897 mRgIndex(0),
5898 mFifRgFirstRowIndex(0),
5899 mRgFirstRowIndex(0),
5900 mRgLastRowIndex(0),
5901 mColIndex(0),
5902 mRowIndex(0),
5903 mIsNewRow(false),
5904 mAtEnd(false),
5905 mPrevRow(nullptr),
5906 mRow(nullptr),
5907 mStartRow(nullptr),
5908 mPrevCell(nullptr),
5909 mCell(nullptr),
5910 mPrevCellData(nullptr),
5911 mCellData(nullptr),
5912 mBCData(nullptr),
5913 mInitialOffsetI(0),
5914 mNextOffsetB(0),
5915 mPrevInlineSegBSize(0) {
5916 MOZ_ASSERT(mTable->IsBorderCollapse(),
5917 "Why are we here if the table is not border-collapsed?");
5919 const LogicalMargin bp = mTable->GetIncludedOuterBCBorder(mTableWM);
5920 // block position of first row in damage area
5921 mInitialOffsetB = mTable->GetPrevInFlow() ? 0 : bp.BStart(mTableWM);
5922 mNumTableRows = mTable->GetRowCount();
5923 mNumTableCols = mTable->GetColCount();
5925 // initialize to a non existing index
5926 mRepeatedHeaderRowIndex = -99;
5929 bool BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) {
5930 nsSize containerSize = mTable->GetSize();
5931 LogicalRect dirtyRect(mTableWM, aDirtyRect, containerSize);
5932 uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex;
5933 startRowIndex = endRowIndex = startColIndex = endColIndex = 0;
5934 bool done = false;
5935 bool haveIntersect = false;
5936 // find startRowIndex, endRowIndex
5937 nscoord rowB = mInitialOffsetB;
5938 nsPresContext* presContext = mTable->PresContext();
5939 for (uint32_t rgIdx = 0; rgIdx < mRowGroups.Length() && !done; rgIdx++) {
5940 nsTableRowGroupFrame* rgFrame = mRowGroups[rgIdx];
5941 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
5942 rowFrame = rowFrame->GetNextRow()) {
5943 // get the row rect relative to the table rather than the row group
5944 nscoord rowBSize = rowFrame->BSize(mTableWM);
5945 if (haveIntersect) {
5946 // conservatively estimate the half border widths outside the row
5947 nscoord borderHalf = mTable->GetPrevInFlow()
5949 : presContext->DevPixelsToAppUnits(
5950 rowFrame->GetBStartBCBorderWidth() + 1);
5952 if (dirtyRect.BEnd(mTableWM) >= rowB - borderHalf) {
5953 nsTableRowFrame* fifRow =
5954 static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
5955 endRowIndex = fifRow->GetRowIndex();
5956 } else
5957 done = true;
5958 } else {
5959 // conservatively estimate the half border widths outside the row
5960 nscoord borderHalf = mTable->GetNextInFlow()
5962 : presContext->DevPixelsToAppUnits(
5963 rowFrame->GetBEndBCBorderWidth() + 1);
5964 if (rowB + rowBSize + borderHalf >= dirtyRect.BStart(mTableWM)) {
5965 mStartRg = rgFrame;
5966 mStartRow = rowFrame;
5967 nsTableRowFrame* fifRow =
5968 static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
5969 startRowIndex = endRowIndex = fifRow->GetRowIndex();
5970 haveIntersect = true;
5971 } else {
5972 mInitialOffsetB += rowBSize;
5975 rowB += rowBSize;
5978 mNextOffsetB = mInitialOffsetB;
5980 // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag
5981 // XXX but I don't understand it, so not changing it for now
5982 // table wrapper borders overflow the table, so the table might be
5983 // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set
5984 // on the table
5985 if (!haveIntersect) return false;
5986 // find startColIndex, endColIndex, startColX
5987 haveIntersect = false;
5988 if (0 == mNumTableCols) return false;
5990 LogicalMargin bp = mTable->GetIncludedOuterBCBorder(mTableWM);
5992 // inline position of first col in damage area
5993 mInitialOffsetI = bp.IStart(mTableWM);
5995 nscoord x = 0;
5996 int32_t colIdx;
5997 for (colIdx = 0; colIdx != mNumTableCols; colIdx++) {
5998 nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colIdx);
5999 if (!colFrame) ABORT1(false);
6000 // get the col rect relative to the table rather than the col group
6001 nscoord colISize = colFrame->ISize(mTableWM);
6002 if (haveIntersect) {
6003 // conservatively estimate the iStart half border width outside the col
6004 nscoord iStartBorderHalf = presContext->DevPixelsToAppUnits(
6005 colFrame->GetIStartBorderWidth() + 1);
6006 if (dirtyRect.IEnd(mTableWM) >= x - iStartBorderHalf) {
6007 endColIndex = colIdx;
6008 } else
6009 break;
6010 } else {
6011 // conservatively estimate the iEnd half border width outside the col
6012 nscoord iEndBorderHalf =
6013 presContext->DevPixelsToAppUnits(colFrame->GetIEndBorderWidth() + 1);
6014 if (x + colISize + iEndBorderHalf >= dirtyRect.IStart(mTableWM)) {
6015 startColIndex = endColIndex = colIdx;
6016 haveIntersect = true;
6017 } else {
6018 mInitialOffsetI += colISize;
6021 x += colISize;
6023 if (!haveIntersect) return false;
6024 mDamageArea =
6025 TableArea(startColIndex, startRowIndex,
6026 1 + DeprecatedAbs<int32_t>(endColIndex - startColIndex),
6027 1 + endRowIndex - startRowIndex);
6029 Reset();
6030 mBlockDirInfo = MakeUnique<BCBlockDirSeg[]>(mDamageArea.ColCount() + 1);
6031 return true;
6034 void BCPaintBorderIterator::Reset() {
6035 mAtEnd = true; // gets reset when First() is called
6036 mRg = mStartRg;
6037 mPrevRow = nullptr;
6038 mRow = mStartRow;
6039 mRowIndex = 0;
6040 mColIndex = 0;
6041 mRgIndex = -1;
6042 mPrevCell = nullptr;
6043 mCell = nullptr;
6044 mPrevCellData = nullptr;
6045 mCellData = nullptr;
6046 mBCData = nullptr;
6047 ResetVerInfo();
6051 * Set the iterator data to a new cellmap coordinate
6052 * @param aRowIndex - the row index
6053 * @param aColIndex - the col index
6055 void BCPaintBorderIterator::SetNewData(int32_t aY, int32_t aX) {
6056 if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0();
6058 mColIndex = aX;
6059 mRowIndex = aY;
6060 mPrevCellData = mCellData;
6061 if (IsTableIEndMost() && IsTableBEndMost()) {
6062 mCell = nullptr;
6063 mBCData = &mTableCellMap->mBCInfo->mBEndIEndCorner;
6064 } else if (IsTableIEndMost()) {
6065 mCellData = nullptr;
6066 mBCData = &mTableCellMap->mBCInfo->mIEndBorders.ElementAt(aY);
6067 } else if (IsTableBEndMost()) {
6068 mCellData = nullptr;
6069 mBCData = &mTableCellMap->mBCInfo->mBEndBorders.ElementAt(aX);
6070 } else {
6071 // We should have set mCellMap during SetNewRowGroup, but if we failed to
6072 // find the appropriate map there, let's just give up.
6073 // Bailing out here may leave us with some missing borders, but seems
6074 // preferable to crashing. (Bug 1442018)
6075 if (MOZ_UNLIKELY(!mCellMap)) {
6076 ABORT0();
6078 if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) {
6079 mBCData = nullptr;
6080 mCellData = (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex]
6081 .SafeElementAt(mColIndex);
6082 if (mCellData) {
6083 mBCData = &mCellData->mData;
6084 if (!mCellData->IsOrig()) {
6085 if (mCellData->IsRowSpan()) {
6086 aY -= mCellData->GetRowSpanOffset();
6088 if (mCellData->IsColSpan()) {
6089 aX -= mCellData->GetColSpanOffset();
6091 if ((aX >= 0) && (aY >= 0)) {
6092 mCellData =
6093 (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX];
6096 if (mCellData->IsOrig()) {
6097 mPrevCell = mCell;
6098 mCell = mCellData->GetCellFrame();
6106 * Set the iterator to a new row
6107 * @param aRow - the new row frame, if null the iterator will advance to the
6108 * next row
6110 bool BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow) {
6111 mPrevRow = mRow;
6112 mRow = (aRow) ? aRow : mRow->GetNextRow();
6113 if (mRow) {
6114 mIsNewRow = true;
6115 mRowIndex = mRow->GetRowIndex();
6116 mColIndex = mDamageArea.StartCol();
6117 mPrevInlineSegBSize = 0;
6118 if (mIsRepeatedHeader) {
6119 mRepeatedHeaderRowIndex = mRowIndex;
6121 } else {
6122 mAtEnd = true;
6124 return !mAtEnd;
6128 * Advance the iterator to the next row group
6130 bool BCPaintBorderIterator::SetNewRowGroup() {
6131 mRgIndex++;
6133 mIsRepeatedHeader = false;
6134 mIsRepeatedFooter = false;
6136 NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds");
6137 if (uint32_t(mRgIndex) < mRowGroups.Length()) {
6138 mPrevRg = mRg;
6139 mRg = mRowGroups[mRgIndex];
6140 nsTableRowGroupFrame* fifRg =
6141 static_cast<nsTableRowGroupFrame*>(mRg->FirstInFlow());
6142 mFifRgFirstRowIndex = fifRg->GetStartRowIndex();
6143 mRgFirstRowIndex = mRg->GetStartRowIndex();
6144 mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1;
6146 if (SetNewRow(mRg->GetFirstRow())) {
6147 mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr);
6148 if (!mCellMap) ABORT1(false);
6150 if (mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) {
6151 // if mRowGroup doesn't have a prev in flow, then it may be a repeated
6152 // header or footer
6153 const nsStyleDisplay* display = mRg->StyleDisplay();
6154 if (mRowIndex == mDamageArea.StartRow()) {
6155 mIsRepeatedHeader =
6156 (mozilla::StyleDisplay::TableHeaderGroup == display->mDisplay);
6157 } else {
6158 mIsRepeatedFooter =
6159 (mozilla::StyleDisplay::TableFooterGroup == display->mDisplay);
6162 } else {
6163 mAtEnd = true;
6165 return !mAtEnd;
6169 * Move the iterator to the first position in the damageArea
6171 void BCPaintBorderIterator::First() {
6172 if (!mTable || mDamageArea.StartCol() >= mNumTableCols ||
6173 mDamageArea.StartRow() >= mNumTableRows)
6174 ABORT0();
6176 mAtEnd = false;
6178 uint32_t numRowGroups = mRowGroups.Length();
6179 for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) {
6180 nsTableRowGroupFrame* rowG = mRowGroups[rgY];
6181 int32_t start = rowG->GetStartRowIndex();
6182 int32_t end = start + rowG->GetRowCount() - 1;
6183 if (mDamageArea.StartRow() >= start && mDamageArea.StartRow() <= end) {
6184 mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex
6185 if (SetNewRowGroup()) {
6186 while (mRowIndex < mDamageArea.StartRow() && !mAtEnd) {
6187 SetNewRow();
6189 if (!mAtEnd) {
6190 SetNewData(mDamageArea.StartRow(), mDamageArea.StartCol());
6193 return;
6196 mAtEnd = true;
6200 * Advance the iterator to the next position
6202 void BCPaintBorderIterator::Next() {
6203 if (mAtEnd) ABORT0();
6204 mIsNewRow = false;
6206 mColIndex++;
6207 if (mColIndex > mDamageArea.EndCol()) {
6208 mRowIndex++;
6209 if (mRowIndex == mDamageArea.EndRow()) {
6210 mColIndex = mDamageArea.StartCol();
6211 } else if (mRowIndex < mDamageArea.EndRow()) {
6212 if (mRowIndex <= mRgLastRowIndex) {
6213 SetNewRow();
6214 } else {
6215 SetNewRowGroup();
6217 } else {
6218 mAtEnd = true;
6221 if (!mAtEnd) {
6222 SetNewData(mRowIndex, mColIndex);
6226 // XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine
6227 // them
6228 // XXX Update terminology from physical to logical
6229 /** Compute the vertical offset of a vertical border segment
6230 * @param aCornerOwnerSide - which side owns the corner
6231 * @param aCornerSubWidth - how wide is the nonwinning side of the corner
6232 * @param aHorWidth - how wide is the horizontal edge of the corner
6233 * @param aIsStartOfSeg - does this corner start a new segment
6234 * @param aIsBevel - is this corner beveled
6235 * @return - offset in twips
6237 static nscoord CalcVerCornerOffset(nsPresContext* aPresContext,
6238 LogicalSide aCornerOwnerSide,
6239 BCPixelSize aCornerSubWidth,
6240 BCPixelSize aHorWidth, bool aIsStartOfSeg,
6241 bool aIsBevel) {
6242 nscoord offset = 0;
6243 // XXX These should be replaced with appropriate side-specific macros (which?)
6244 BCPixelSize smallHalf, largeHalf;
6245 if (IsBlock(aCornerOwnerSide)) {
6246 DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
6247 if (aIsBevel) {
6248 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6249 } else {
6250 offset =
6251 (eLogicalSideBStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
6253 } else {
6254 DivideBCBorderSize(aHorWidth, smallHalf, largeHalf);
6255 if (aIsBevel) {
6256 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6257 } else {
6258 offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
6261 return aPresContext->DevPixelsToAppUnits(offset);
6264 /** Compute the horizontal offset of a horizontal border segment
6265 * @param aCornerOwnerSide - which side owns the corner
6266 * @param aCornerSubWidth - how wide is the nonwinning side of the corner
6267 * @param aVerWidth - how wide is the vertical edge of the corner
6268 * @param aIsStartOfSeg - does this corner start a new segment
6269 * @param aIsBevel - is this corner beveled
6270 * @return - offset in twips
6272 static nscoord CalcHorCornerOffset(nsPresContext* aPresContext,
6273 LogicalSide aCornerOwnerSide,
6274 BCPixelSize aCornerSubWidth,
6275 BCPixelSize aVerWidth, bool aIsStartOfSeg,
6276 bool aIsBevel) {
6277 nscoord offset = 0;
6278 // XXX These should be replaced with appropriate side-specific macros (which?)
6279 BCPixelSize smallHalf, largeHalf;
6280 if (IsInline(aCornerOwnerSide)) {
6281 DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
6282 if (aIsBevel) {
6283 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6284 } else {
6285 offset =
6286 (eLogicalSideIStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
6288 } else {
6289 DivideBCBorderSize(aVerWidth, smallHalf, largeHalf);
6290 if (aIsBevel) {
6291 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6292 } else {
6293 offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
6296 return aPresContext->DevPixelsToAppUnits(offset);
6299 BCBlockDirSeg::BCBlockDirSeg()
6300 : mFirstRowGroup(nullptr),
6301 mFirstRow(nullptr),
6302 mBEndInlineSegBSize(0),
6303 mBEndOffset(0),
6304 mIsBEndBevel(false) {
6305 mCol = nullptr;
6306 mFirstCell = mLastCell = mAjaCell = nullptr;
6307 mOffsetI = mOffsetB = mLength = mWidth = mBStartBevelOffset = 0;
6308 mBStartBevelSide = eLogicalSideBStart;
6309 mOwner = eCellOwner;
6313 * Start a new block-direction segment
6314 * @param aIter - iterator containing the structural information
6315 * @param aBorderOwner - determines the border style
6316 * @param aBlockSegISize - the width of segment in pixel
6317 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6318 * corner at the start
6320 void BCBlockDirSeg::Start(BCPaintBorderIterator& aIter,
6321 BCBorderOwner aBorderOwner,
6322 BCPixelSize aBlockSegISize,
6323 BCPixelSize aInlineSegBSize,
6324 Maybe<nscoord> aEmptyRowEndBSize) {
6325 LogicalSide ownerSide = eLogicalSideBStart;
6326 bool bevel = false;
6328 nscoord cornerSubWidth =
6329 (aIter.mBCData) ? aIter.mBCData->GetCorner(ownerSide, bevel) : 0;
6331 bool bStartBevel = (aBlockSegISize > 0) ? bevel : false;
6332 BCPixelSize maxInlineSegBSize =
6333 std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
6334 nsPresContext* presContext = aIter.mTable->PresContext();
6335 nscoord offset = CalcVerCornerOffset(presContext, ownerSide, cornerSubWidth,
6336 maxInlineSegBSize, true, bStartBevel);
6338 mBStartBevelOffset =
6339 bStartBevel ? presContext->DevPixelsToAppUnits(maxInlineSegBSize) : 0;
6340 // XXX this assumes that only corners where 2 segments join can be beveled
6341 mBStartBevelSide =
6342 (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart;
6343 if (aEmptyRowEndBSize && *aEmptyRowEndBSize < offset) {
6344 // This segment is starting from an empty row. This will require the the
6345 // starting segment to overlap with the previously drawn segment, unless the
6346 // empty row's size clears the overlap.
6347 mOffsetB += *aEmptyRowEndBSize;
6348 } else {
6349 mOffsetB += offset;
6351 mLength = -offset;
6352 mWidth = aBlockSegISize;
6353 mOwner = aBorderOwner;
6354 mFirstCell = aIter.mCell;
6355 mFirstRowGroup = aIter.mRg;
6356 mFirstRow = aIter.mRow;
6357 if (aIter.GetRelativeColIndex() > 0) {
6358 mAjaCell = aIter.mBlockDirInfo[aIter.GetRelativeColIndex() - 1].mLastCell;
6363 * Initialize the block-dir segments with information that will persist for any
6364 * block-dir segment in this column
6365 * @param aIter - iterator containing the structural information
6367 void BCBlockDirSeg::Initialize(BCPaintBorderIterator& aIter) {
6368 int32_t relColIndex = aIter.GetRelativeColIndex();
6369 mCol = aIter.IsTableIEndMost()
6370 ? aIter.mBlockDirInfo[relColIndex - 1].mCol
6371 : aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex);
6372 if (!mCol) ABORT0();
6373 if (0 == relColIndex) {
6374 mOffsetI = aIter.mInitialOffsetI;
6376 // set mOffsetI for the next column
6377 if (!aIter.IsDamageAreaIEndMost()) {
6378 aIter.mBlockDirInfo[relColIndex + 1].mOffsetI =
6379 mOffsetI + mCol->ISize(aIter.mTableWM);
6381 mOffsetB = aIter.mInitialOffsetB;
6382 mLastCell = aIter.mCell;
6386 * Compute the offsets for the bEnd corner of a block-dir segment
6387 * @param aIter - iterator containing the structural information
6388 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6389 * corner at the start
6391 void BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator& aIter,
6392 BCPixelSize aInlineSegBSize) {
6393 LogicalSide ownerSide = eLogicalSideBStart;
6394 nscoord cornerSubWidth = 0;
6395 bool bevel = false;
6396 if (aIter.mBCData) {
6397 cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
6399 mIsBEndBevel = (mWidth > 0) ? bevel : false;
6400 mBEndInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
6401 mBEndOffset = CalcVerCornerOffset(aIter.mTable->PresContext(), ownerSide,
6402 cornerSubWidth, mBEndInlineSegBSize, false,
6403 mIsBEndBevel);
6404 mLength += mBEndOffset;
6407 Maybe<BCBorderParameters> BCBlockDirSeg::BuildBorderParameters(
6408 BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize) {
6409 BCBorderParameters result;
6411 // get the border style, color and paint the segment
6412 LogicalSide side =
6413 aIter.IsDamageAreaIEndMost() ? eLogicalSideIEnd : eLogicalSideIStart;
6414 int32_t relColIndex = aIter.GetRelativeColIndex();
6415 nsTableColFrame* col = mCol;
6416 if (!col) ABORT1(Nothing());
6417 nsTableCellFrame* cell = mFirstCell; // ???
6418 nsIFrame* owner = nullptr;
6419 result.mBorderStyle = StyleBorderStyle::Solid;
6420 result.mBorderColor = 0xFFFFFFFF;
6421 result.mBackfaceIsVisible = true;
6423 // All the tables frames have the same presContext, so we just use any one
6424 // that exists here:
6425 nsPresContext* presContext = aIter.mTable->PresContext();
6426 result.mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
6428 switch (mOwner) {
6429 case eTableOwner:
6430 owner = aIter.mTable;
6431 break;
6432 case eAjaColGroupOwner:
6433 side = eLogicalSideIEnd;
6434 if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
6435 col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
6437 [[fallthrough]];
6438 case eColGroupOwner:
6439 if (col) {
6440 owner = col->GetParent();
6442 break;
6443 case eAjaColOwner:
6444 side = eLogicalSideIEnd;
6445 if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
6446 col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
6448 [[fallthrough]];
6449 case eColOwner:
6450 owner = col;
6451 break;
6452 case eAjaRowGroupOwner:
6453 NS_ERROR("a neighboring rowgroup can never own a vertical border");
6454 [[fallthrough]];
6455 case eRowGroupOwner:
6456 NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
6457 "row group can own border only at table edge");
6458 owner = mFirstRowGroup;
6459 break;
6460 case eAjaRowOwner:
6461 NS_ERROR("program error");
6462 [[fallthrough]];
6463 case eRowOwner:
6464 NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
6465 "row can own border only at table edge");
6466 owner = mFirstRow;
6467 break;
6468 case eAjaCellOwner:
6469 side = eLogicalSideIEnd;
6470 cell = mAjaCell;
6471 [[fallthrough]];
6472 case eCellOwner:
6473 owner = cell;
6474 break;
6476 if (owner) {
6477 ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle,
6478 &result.mBorderColor);
6479 result.mBackfaceIsVisible = !owner->BackfaceIsHidden();
6481 BCPixelSize smallHalf, largeHalf;
6482 DivideBCBorderSize(mWidth, smallHalf, largeHalf);
6483 LogicalRect segRect(
6484 aIter.mTableWM, mOffsetI - presContext->DevPixelsToAppUnits(largeHalf),
6485 mOffsetB, presContext->DevPixelsToAppUnits(mWidth), mLength);
6486 nscoord bEndBevelOffset =
6487 (mIsBEndBevel) ? presContext->DevPixelsToAppUnits(mBEndInlineSegBSize)
6488 : 0;
6489 LogicalSide bEndBevelSide =
6490 (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart;
6492 // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
6494 result.mBorderRect =
6495 segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize());
6496 // XXX For reversed vertical writing-modes (with direction:rtl), we need to
6497 // invert physicalRect's y-position here, with respect to the table.
6498 // However, it's not worth fixing the border positions here until the
6499 // ordering of the table columns themselves is also fixed (bug 1180528).
6501 result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide);
6502 result.mEndBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide);
6503 result.mStartBevelOffset = mBStartBevelOffset;
6504 result.mEndBevelOffset = bEndBevelOffset;
6505 // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal)
6506 // border segment need to be swapped because DrawTableBorderSegment will
6507 // apply the 'start' bevel at the left edge, and 'end' at the right.
6508 // (Note: In this case, startBevelSide/endBevelSide will usually both be
6509 // "top" or "bottom". DrawTableBorderSegment works purely with physical
6510 // coordinates, so it expects startBevelOffset to be the indentation-from-
6511 // the-left for the "start" (left) end of the border-segment, and
6512 // endBevelOffset is the indentation-from-the-right for the "end" (right)
6513 // end of the border-segment. We've got them reversed, since our block dir
6514 // is RTL, so we have to swap them here.)
6515 if (aIter.mTableWM.IsVerticalRL()) {
6516 std::swap(result.mStartBevelSide, result.mEndBevelSide);
6517 std::swap(result.mStartBevelOffset, result.mEndBevelOffset);
6520 return Some(result);
6524 * Paint the block-dir segment
6525 * @param aIter - iterator containing the structural information
6526 * @param aDrawTarget - the draw target
6527 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6528 * corner at the start
6530 void BCBlockDirSeg::Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget,
6531 BCPixelSize aInlineSegBSize) {
6532 Maybe<BCBorderParameters> param =
6533 BuildBorderParameters(aIter, aInlineSegBSize);
6534 if (param.isNothing()) {
6535 return;
6538 nsCSSRendering::DrawTableBorderSegment(
6539 aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect,
6540 param->mAppUnitsPerDevPixel, param->mStartBevelSide,
6541 param->mStartBevelOffset, param->mEndBevelSide, param->mEndBevelOffset);
6544 // Pushes a border bevel triangle and substracts the relevant rectangle from
6545 // aRect, which, after all the bevels, will end up being a solid segment rect.
6546 static void AdjustAndPushBevel(wr::DisplayListBuilder& aBuilder,
6547 wr::LayoutRect& aRect, nscolor aColor,
6548 const nsCSSRendering::Bevel& aBevel,
6549 int32_t aAppUnitsPerDevPixel,
6550 bool aBackfaceIsVisible, bool aIsStart) {
6551 if (!aBevel.mOffset) {
6552 return;
6555 const auto kTransparent = wr::ToColorF(gfx::DeviceColor(0., 0., 0., 0.));
6556 const bool horizontal =
6557 aBevel.mSide == eSideTop || aBevel.mSide == eSideBottom;
6559 // Crappy CSS triangle as known by every web developer ever :)
6560 Float offset = NSAppUnitsToFloatPixels(aBevel.mOffset, aAppUnitsPerDevPixel);
6561 wr::LayoutRect bevelRect = aRect;
6562 wr::BorderSide bevelBorder[4];
6563 for (const auto i : mozilla::AllPhysicalSides()) {
6564 bevelBorder[i] =
6565 wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid);
6568 // We're creating a half-transparent triangle using the border primitive.
6570 // Classic web-dev trick, with a gotcha: we use a single corner to avoid
6571 // seams and rounding errors.
6573 // Classic web-dev trick :P
6574 auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0);
6575 bevelBorder[aBevel.mSide].color = kTransparent;
6576 if (aIsStart) {
6577 if (horizontal) {
6578 bevelBorder[eSideLeft].color = kTransparent;
6579 borderWidths.left = offset;
6580 } else {
6581 bevelBorder[eSideTop].color = kTransparent;
6582 borderWidths.top = offset;
6584 } else {
6585 if (horizontal) {
6586 bevelBorder[eSideRight].color = kTransparent;
6587 borderWidths.right = offset;
6588 } else {
6589 bevelBorder[eSideBottom].color = kTransparent;
6590 borderWidths.bottom = offset;
6594 if (horizontal) {
6595 if (aIsStart) {
6596 aRect.min.x += offset;
6597 aRect.max.x += offset;
6598 } else {
6599 bevelRect.min.x += aRect.width() - offset;
6600 bevelRect.max.x += aRect.width() - offset;
6602 aRect.max.x -= offset;
6603 bevelRect.max.y = bevelRect.min.y + aRect.height();
6604 bevelRect.max.x = bevelRect.min.x + offset;
6605 if (aBevel.mSide == eSideTop) {
6606 borderWidths.bottom = aRect.height();
6607 } else {
6608 borderWidths.top = aRect.height();
6610 } else {
6611 if (aIsStart) {
6612 aRect.min.y += offset;
6613 aRect.max.y += offset;
6614 } else {
6615 bevelRect.min.y += aRect.height() - offset;
6616 bevelRect.max.y += aRect.height() - offset;
6618 aRect.max.y -= offset;
6619 bevelRect.max.x = bevelRect.min.x + aRect.width();
6620 bevelRect.max.y = bevelRect.min.y + offset;
6621 if (aBevel.mSide == eSideLeft) {
6622 borderWidths.right = aRect.width();
6623 } else {
6624 borderWidths.left = aRect.width();
6628 Range<const wr::BorderSide> wrsides(bevelBorder, 4);
6629 // It's important to _not_ anti-alias the bevel, because otherwise we wouldn't
6630 // be able bevel to sides of the same color without bleeding in the middle.
6631 aBuilder.PushBorder(bevelRect, bevelRect, aBackfaceIsVisible, borderWidths,
6632 wrsides, wr::EmptyBorderRadius(),
6633 wr::AntialiasBorder::No);
6636 static void CreateWRCommandsForBeveledBorder(
6637 const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder,
6638 const layers::StackingContextHelper& aSc, const nsPoint& aOffset) {
6639 MOZ_ASSERT(aBorderParams.NeedToBevel());
6641 AutoTArray<nsCSSRendering::SolidBeveledBorderSegment, 3> segments;
6642 nsCSSRendering::GetTableBorderSolidSegments(
6643 segments, aBorderParams.mBorderStyle, aBorderParams.mBorderColor,
6644 aBorderParams.mBorderRect, aBorderParams.mAppUnitsPerDevPixel,
6645 aBorderParams.mStartBevelSide, aBorderParams.mStartBevelOffset,
6646 aBorderParams.mEndBevelSide, aBorderParams.mEndBevelOffset);
6648 for (const auto& segment : segments) {
6649 auto rect = LayoutDeviceRect::FromUnknownRect(NSRectToRect(
6650 segment.mRect + aOffset, aBorderParams.mAppUnitsPerDevPixel));
6651 auto r = wr::ToLayoutRect(rect);
6652 auto color = wr::ToColorF(ToDeviceColor(segment.mColor));
6654 // Adjust for the start bevel if needed.
6655 AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mStartBevel,
6656 aBorderParams.mAppUnitsPerDevPixel,
6657 aBorderParams.mBackfaceIsVisible, true);
6659 AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mEndBevel,
6660 aBorderParams.mAppUnitsPerDevPixel,
6661 aBorderParams.mBackfaceIsVisible, false);
6663 aBuilder.PushRect(r, r, aBorderParams.mBackfaceIsVisible, false, false,
6664 color);
6668 static void CreateWRCommandsForBorderSegment(
6669 const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder,
6670 const layers::StackingContextHelper& aSc, const nsPoint& aOffset) {
6671 if (aBorderParams.NeedToBevel()) {
6672 CreateWRCommandsForBeveledBorder(aBorderParams, aBuilder, aSc, aOffset);
6673 return;
6676 auto borderRect = LayoutDeviceRect::FromUnknownRect(NSRectToRect(
6677 aBorderParams.mBorderRect + aOffset, aBorderParams.mAppUnitsPerDevPixel));
6679 wr::LayoutRect r = wr::ToLayoutRect(borderRect);
6680 wr::BorderSide wrSide[4];
6681 for (const auto i : mozilla::AllPhysicalSides()) {
6682 wrSide[i] = wr::ToBorderSide(ToDeviceColor(aBorderParams.mBorderColor),
6683 StyleBorderStyle::None);
6685 const bool horizontal = aBorderParams.mStartBevelSide == eSideTop ||
6686 aBorderParams.mStartBevelSide == eSideBottom;
6687 auto borderWidth = horizontal ? r.height() : r.width();
6689 // All border style is set to none except left side. So setting the widths of
6690 // each side to width of rect is fine.
6691 auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0);
6693 wrSide[horizontal ? eSideTop : eSideLeft] = wr::ToBorderSide(
6694 ToDeviceColor(aBorderParams.mBorderColor), aBorderParams.mBorderStyle);
6696 if (horizontal) {
6697 borderWidths.top = borderWidth;
6698 } else {
6699 borderWidths.left = borderWidth;
6702 Range<const wr::BorderSide> wrsides(wrSide, 4);
6703 aBuilder.PushBorder(r, r, aBorderParams.mBackfaceIsVisible, borderWidths,
6704 wrsides, wr::EmptyBorderRadius());
6707 void BCBlockDirSeg::CreateWebRenderCommands(
6708 BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize,
6709 wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc,
6710 const nsPoint& aOffset) {
6711 Maybe<BCBorderParameters> param =
6712 BuildBorderParameters(aIter, aInlineSegBSize);
6713 if (param.isNothing()) {
6714 return;
6717 CreateWRCommandsForBorderSegment(*param, aBuilder, aSc, aOffset);
6721 * Advance the start point of a segment
6723 void BCBlockDirSeg::AdvanceOffsetB() { mOffsetB += mLength - mBEndOffset; }
6726 * Accumulate the current segment
6728 void BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) {
6729 mLastCell = aIter.mCell;
6730 mLength += aIter.mRow->BSize(aIter.mTableWM);
6733 BCInlineDirSeg::BCInlineDirSeg()
6734 : mIsIEndBevel(false),
6735 mIEndBevelOffset(0),
6736 mIEndBevelSide(eLogicalSideBStart),
6737 mEndOffset(0),
6738 mOwner(eTableOwner) {
6739 mOffsetI = mOffsetB = mLength = mWidth = mIStartBevelOffset = 0;
6740 mIStartBevelSide = eLogicalSideBStart;
6741 mFirstCell = mAjaCell = nullptr;
6744 /** Initialize an inline-dir border segment for painting
6745 * @param aIter - iterator storing the current and adjacent frames
6746 * @param aBorderOwner - which frame owns the border
6747 * @param aBEndBlockSegISize - block-dir segment width coming from up
6748 * @param aInlineSegBSize - the thickness of the segment
6749 + */
6750 void BCInlineDirSeg::Start(BCPaintBorderIterator& aIter,
6751 BCBorderOwner aBorderOwner,
6752 BCPixelSize aBEndBlockSegISize,
6753 BCPixelSize aInlineSegBSize) {
6754 LogicalSide cornerOwnerSide = eLogicalSideBStart;
6755 bool bevel = false;
6757 mOwner = aBorderOwner;
6758 nscoord cornerSubWidth =
6759 (aIter.mBCData) ? aIter.mBCData->GetCorner(cornerOwnerSide, bevel) : 0;
6761 bool iStartBevel = (aInlineSegBSize > 0) ? bevel : false;
6762 int32_t relColIndex = aIter.GetRelativeColIndex();
6763 nscoord maxBlockSegISize =
6764 std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aBEndBlockSegISize);
6765 nscoord offset =
6766 CalcHorCornerOffset(aIter.mTable->PresContext(), cornerOwnerSide,
6767 cornerSubWidth, maxBlockSegISize, true, iStartBevel);
6768 mIStartBevelOffset =
6769 (iStartBevel && (aInlineSegBSize > 0)) ? maxBlockSegISize : 0;
6770 // XXX this assumes that only corners where 2 segments join can be beveled
6771 mIStartBevelSide =
6772 (aBEndBlockSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart;
6773 mOffsetI += offset;
6774 mLength = -offset;
6775 mWidth = aInlineSegBSize;
6776 mFirstCell = aIter.mCell;
6777 mAjaCell = (aIter.IsDamageAreaBStartMost())
6778 ? nullptr
6779 : aIter.mBlockDirInfo[relColIndex].mLastCell;
6783 * Compute the offsets for the iEnd corner of an inline-dir segment
6784 * @param aIter - iterator containing the structural information
6785 * @param aIStartSegISize - the iSize of the block-dir segment joining the
6786 * corner at the start
6788 void BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator& aIter,
6789 BCPixelSize aIStartSegISize) {
6790 LogicalSide ownerSide = eLogicalSideBStart;
6791 nscoord cornerSubWidth = 0;
6792 bool bevel = false;
6793 if (aIter.mBCData) {
6794 cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
6797 mIsIEndBevel = (mWidth > 0) ? bevel : 0;
6798 int32_t relColIndex = aIter.GetRelativeColIndex();
6799 nscoord verWidth =
6800 std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aIStartSegISize);
6801 nsPresContext* presContext = aIter.mTable->PresContext();
6802 mEndOffset = CalcHorCornerOffset(presContext, ownerSide, cornerSubWidth,
6803 verWidth, false, mIsIEndBevel);
6804 mLength += mEndOffset;
6805 mIEndBevelOffset =
6806 (mIsIEndBevel) ? presContext->DevPixelsToAppUnits(verWidth) : 0;
6807 mIEndBevelSide =
6808 (aIStartSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart;
6811 Maybe<BCBorderParameters> BCInlineDirSeg::BuildBorderParameters(
6812 BCPaintBorderIterator& aIter) {
6813 BCBorderParameters result;
6815 // get the border style, color and paint the segment
6816 LogicalSide side =
6817 aIter.IsDamageAreaBEndMost() ? eLogicalSideBEnd : eLogicalSideBStart;
6818 nsIFrame* rg = aIter.mRg;
6819 if (!rg) ABORT1(Nothing());
6820 nsIFrame* row = aIter.mRow;
6821 if (!row) ABORT1(Nothing());
6822 nsIFrame* cell = mFirstCell;
6823 nsIFrame* col;
6824 nsIFrame* owner = nullptr;
6825 result.mBackfaceIsVisible = true;
6827 // All the tables frames have the same presContext, so we just use any one
6828 // that exists here:
6829 nsPresContext* presContext = aIter.mTable->PresContext();
6830 result.mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
6832 result.mBorderStyle = StyleBorderStyle::Solid;
6833 result.mBorderColor = 0xFFFFFFFF;
6835 switch (mOwner) {
6836 case eTableOwner:
6837 owner = aIter.mTable;
6838 break;
6839 case eAjaColGroupOwner:
6840 NS_ERROR("neighboring colgroups can never own an inline-dir border");
6841 [[fallthrough]];
6842 case eColGroupOwner:
6843 NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
6844 "col group can own border only at the table edge");
6845 col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
6846 if (!col) ABORT1(Nothing());
6847 owner = col->GetParent();
6848 break;
6849 case eAjaColOwner:
6850 NS_ERROR("neighboring column can never own an inline-dir border");
6851 [[fallthrough]];
6852 case eColOwner:
6853 NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
6854 "col can own border only at the table edge");
6855 owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
6856 break;
6857 case eAjaRowGroupOwner:
6858 side = eLogicalSideBEnd;
6859 rg = (aIter.IsTableBEndMost()) ? aIter.mRg : aIter.mPrevRg;
6860 [[fallthrough]];
6861 case eRowGroupOwner:
6862 owner = rg;
6863 break;
6864 case eAjaRowOwner:
6865 side = eLogicalSideBEnd;
6866 row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow;
6867 [[fallthrough]];
6868 case eRowOwner:
6869 owner = row;
6870 break;
6871 case eAjaCellOwner:
6872 side = eLogicalSideBEnd;
6873 // if this is null due to the damage area origin-y > 0, then the border
6874 // won't show up anyway
6875 cell = mAjaCell;
6876 [[fallthrough]];
6877 case eCellOwner:
6878 owner = cell;
6879 break;
6881 if (owner) {
6882 ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle,
6883 &result.mBorderColor);
6884 result.mBackfaceIsVisible = !owner->BackfaceIsHidden();
6886 BCPixelSize smallHalf, largeHalf;
6887 DivideBCBorderSize(mWidth, smallHalf, largeHalf);
6888 LogicalRect segRect(aIter.mTableWM, mOffsetI,
6889 mOffsetB - presContext->DevPixelsToAppUnits(largeHalf),
6890 mLength, presContext->DevPixelsToAppUnits(mWidth));
6892 // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
6893 result.mBorderRect =
6894 segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize());
6895 result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide);
6896 result.mEndBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide);
6897 result.mStartBevelOffset =
6898 presContext->DevPixelsToAppUnits(mIStartBevelOffset);
6899 result.mEndBevelOffset = mIEndBevelOffset;
6900 // With inline-RTL directionality, the 'start' and 'end' of the inline-dir
6901 // border segment need to be swapped because DrawTableBorderSegment will
6902 // apply the 'start' bevel physically at the left or top edge, and 'end' at
6903 // the right or bottom.
6904 // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal
6905 // writing mode, or "left" or "right" in vertical mode.
6906 // DrawTableBorderSegment works purely with physical coordinates, so it
6907 // expects startBevelOffset to be the indentation-from-the-left or top end
6908 // of the border-segment, and endBevelOffset is the indentation-from-the-
6909 // right or bottom end. If the writing mode is inline-RTL, our "start" and
6910 // "end" will be reversed from this physical-coord view, so we have to swap
6911 // them here.
6912 if (aIter.mTableWM.IsBidiRTL()) {
6913 std::swap(result.mStartBevelSide, result.mEndBevelSide);
6914 std::swap(result.mStartBevelOffset, result.mEndBevelOffset);
6917 return Some(result);
6921 * Paint the inline-dir segment
6922 * @param aIter - iterator containing the structural information
6923 * @param aDrawTarget - the draw target
6925 void BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter,
6926 DrawTarget& aDrawTarget) {
6927 Maybe<BCBorderParameters> param = BuildBorderParameters(aIter);
6928 if (param.isNothing()) {
6929 return;
6932 nsCSSRendering::DrawTableBorderSegment(
6933 aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect,
6934 param->mAppUnitsPerDevPixel, param->mStartBevelSide,
6935 param->mStartBevelOffset, param->mEndBevelSide, param->mEndBevelOffset);
6938 void BCInlineDirSeg::CreateWebRenderCommands(
6939 BCPaintBorderIterator& aIter, wr::DisplayListBuilder& aBuilder,
6940 const layers::StackingContextHelper& aSc, const nsPoint& aPt) {
6941 Maybe<BCBorderParameters> param = BuildBorderParameters(aIter);
6942 if (param.isNothing()) {
6943 return;
6946 CreateWRCommandsForBorderSegment(*param, aBuilder, aSc, aPt);
6950 * Advance the start point of a segment
6952 void BCInlineDirSeg::AdvanceOffsetI() { mOffsetI += (mLength - mEndOffset); }
6955 * Accumulate the current segment
6957 void BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) {
6958 mLength += aIter.mBlockDirInfo[aIter.GetRelativeColIndex()].mColWidth;
6962 * store the column width information while painting inline-dir segment
6964 void BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) {
6965 if (IsTableIEndMost()) {
6966 mBlockDirInfo[aIndex].mColWidth = mBlockDirInfo[aIndex - 1].mColWidth;
6967 } else {
6968 nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex);
6969 if (!col) ABORT0();
6970 mBlockDirInfo[aIndex].mColWidth = col->ISize(mTableWM);
6974 * Determine if a block-dir segment owns the corner
6976 bool BCPaintBorderIterator::BlockDirSegmentOwnsCorner() {
6977 LogicalSide cornerOwnerSide = eLogicalSideBStart;
6978 bool bevel = false;
6979 if (mBCData) {
6980 mBCData->GetCorner(cornerOwnerSide, bevel);
6982 // unitialized ownerside, bevel
6983 return (eLogicalSideBStart == cornerOwnerSide) ||
6984 (eLogicalSideBEnd == cornerOwnerSide);
6988 * Paint if necessary an inline-dir segment, otherwise accumulate it
6989 * @param aDrawTarget - the draw target
6991 void BCPaintBorderIterator::AccumulateOrDoActionInlineDirSegment(
6992 BCPaintBorderAction& aAction) {
6993 int32_t relColIndex = GetRelativeColIndex();
6994 // store the current col width if it hasn't been already
6995 if (mBlockDirInfo[relColIndex].mColWidth < 0) {
6996 StoreColumnWidth(relColIndex);
6999 BCBorderOwner borderOwner = eCellOwner;
7000 BCBorderOwner ignoreBorderOwner;
7001 bool isSegStart = true;
7002 bool ignoreSegStart;
7004 nscoord iStartSegISize =
7005 mBCData ? mBCData->GetIStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
7006 nscoord bStartSegBSize =
7007 mBCData ? mBCData->GetBStartEdge(borderOwner, isSegStart) : 0;
7009 if (mIsNewRow || (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) {
7010 // reset for every new row and on the bottom of the last row
7011 mInlineSeg.mOffsetB = mNextOffsetB;
7012 mNextOffsetB = mNextOffsetB + mRow->BSize(mTableWM);
7013 mInlineSeg.mOffsetI = mInitialOffsetI;
7014 mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
7017 if (!IsDamageAreaIStartMost() &&
7018 (isSegStart || IsDamageAreaIEndMost() || BlockDirSegmentOwnsCorner())) {
7019 // paint the previous seg or the current one if IsDamageAreaIEndMost()
7020 if (mInlineSeg.mLength > 0) {
7021 mInlineSeg.GetIEndCorner(*this, iStartSegISize);
7022 if (mInlineSeg.mWidth > 0) {
7023 if (aAction.mMode == BCPaintBorderAction::Mode::Paint) {
7024 mInlineSeg.Paint(*this, aAction.mPaintData.mDrawTarget);
7025 } else {
7026 MOZ_ASSERT(aAction.mMode ==
7027 BCPaintBorderAction::Mode::CreateWebRenderCommands);
7028 mInlineSeg.CreateWebRenderCommands(
7029 *this, aAction.mCreateWebRenderCommandsData.mBuilder,
7030 aAction.mCreateWebRenderCommandsData.mSc,
7031 aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame);
7034 mInlineSeg.AdvanceOffsetI();
7036 mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
7038 mInlineSeg.IncludeCurrentBorder(*this);
7039 mBlockDirInfo[relColIndex].mWidth = iStartSegISize;
7040 mBlockDirInfo[relColIndex].mLastCell = mCell;
7044 * Paint if necessary a block-dir segment, otherwise accumulate it
7045 * @param aDrawTarget - the draw target
7047 void BCPaintBorderIterator::AccumulateOrDoActionBlockDirSegment(
7048 BCPaintBorderAction& aAction) {
7049 BCBorderOwner borderOwner = eCellOwner;
7050 BCBorderOwner ignoreBorderOwner;
7051 bool isSegStart = true;
7052 bool ignoreSegStart;
7054 nscoord blockSegISize =
7055 mBCData ? mBCData->GetIStartEdge(borderOwner, isSegStart) : 0;
7056 nscoord inlineSegBSize =
7057 mBCData ? mBCData->GetBStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
7059 int32_t relColIndex = GetRelativeColIndex();
7060 BCBlockDirSeg& blockDirSeg = mBlockDirInfo[relColIndex];
7061 if (!blockDirSeg.mCol) { // on the first damaged row and the first segment in
7062 // the col
7063 blockDirSeg.Initialize(*this);
7064 blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize,
7065 Nothing{});
7068 if (!IsDamageAreaBStartMost() &&
7069 (isSegStart || IsDamageAreaBEndMost() || IsAfterRepeatedHeader() ||
7070 StartRepeatedFooter())) {
7071 Maybe<nscoord> emptyRowEndSize;
7072 // paint the previous seg or the current one if IsDamageAreaBEndMost()
7073 if (blockDirSeg.mLength > 0) {
7074 blockDirSeg.GetBEndCorner(*this, inlineSegBSize);
7075 if (blockDirSeg.mWidth > 0) {
7076 if (aAction.mMode == BCPaintBorderAction::Mode::Paint) {
7077 blockDirSeg.Paint(*this, aAction.mPaintData.mDrawTarget,
7078 inlineSegBSize);
7079 } else {
7080 MOZ_ASSERT(aAction.mMode ==
7081 BCPaintBorderAction::Mode::CreateWebRenderCommands);
7082 blockDirSeg.CreateWebRenderCommands(
7083 *this, inlineSegBSize,
7084 aAction.mCreateWebRenderCommandsData.mBuilder,
7085 aAction.mCreateWebRenderCommandsData.mSc,
7086 aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame);
7089 blockDirSeg.AdvanceOffsetB();
7090 if (mRow->PrincipalChildList().IsEmpty()) {
7091 emptyRowEndSize = Some(mRow->BSize(mTableWM));
7094 blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize,
7095 emptyRowEndSize);
7097 blockDirSeg.IncludeCurrentBorder(*this);
7098 mPrevInlineSegBSize = inlineSegBSize;
7102 * Reset the block-dir information cache
7104 void BCPaintBorderIterator::ResetVerInfo() {
7105 if (mBlockDirInfo) {
7106 memset(mBlockDirInfo.get(), 0,
7107 mDamageArea.ColCount() * sizeof(BCBlockDirSeg));
7108 // XXX reinitialize properly
7109 for (auto xIndex : IntegerRange(mDamageArea.ColCount())) {
7110 mBlockDirInfo[xIndex].mColWidth = -1;
7115 void nsTableFrame::IterateBCBorders(BCPaintBorderAction& aAction,
7116 const nsRect& aDirtyRect) {
7117 // We first transfer the aDirtyRect into cellmap coordinates to compute which
7118 // cell borders need to be painted
7119 BCPaintBorderIterator iter(this);
7120 if (!iter.SetDamageArea(aDirtyRect)) return;
7122 // XXX comment still has physical terminology
7123 // First, paint all of the vertical borders from top to bottom and left to
7124 // right as they become complete. They are painted first, since they are less
7125 // efficient to paint than horizontal segments. They were stored with as few
7126 // segments as possible (since horizontal borders are painted last and
7127 // possibly over them). For every cell in a row that fails in the damage are
7128 // we look up if the current border would start a new segment, if so we paint
7129 // the previously stored vertical segment and start a new segment. After
7130 // this we the now active segment with the current border. These
7131 // segments are stored in mBlockDirInfo to be used on the next row
7132 for (iter.First(); !iter.mAtEnd; iter.Next()) {
7133 iter.AccumulateOrDoActionBlockDirSegment(aAction);
7136 // Next, paint all of the inline-dir border segments from bStart to bEnd reuse
7137 // the mBlockDirInfo array to keep track of col widths and block-dir segments
7138 // for corner calculations
7139 iter.Reset();
7140 for (iter.First(); !iter.mAtEnd; iter.Next()) {
7141 iter.AccumulateOrDoActionInlineDirSegment(aAction);
7146 * Method to paint BCBorders, this does not use currently display lists although
7147 * it will do this in future
7148 * @param aDrawTarget - the rendering context
7149 * @param aDirtyRect - inside this rectangle the BC Borders will redrawn
7151 void nsTableFrame::PaintBCBorders(DrawTarget& aDrawTarget,
7152 const nsRect& aDirtyRect) {
7153 BCPaintBorderAction action(aDrawTarget);
7154 IterateBCBorders(action, aDirtyRect);
7157 void nsTableFrame::CreateWebRenderCommandsForBCBorders(
7158 wr::DisplayListBuilder& aBuilder,
7159 const mozilla::layers::StackingContextHelper& aSc,
7160 const nsRect& aVisibleRect, const nsPoint& aOffsetToReferenceFrame) {
7161 BCPaintBorderAction action(aBuilder, aSc, aOffsetToReferenceFrame);
7162 // We always draw whole table border for webrender. Passing the visible rect
7163 // dirty rect.
7164 IterateBCBorders(action, aVisibleRect - aOffsetToReferenceFrame);
7167 bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) {
7168 bool result = false;
7169 nsTableCellMap* cellMap = GetCellMap();
7170 MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated.");
7171 if (cellMap) {
7172 result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols);
7174 return result;
7177 bool nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) {
7178 bool result = false;
7179 nsTableCellMap* cellMap = GetCellMap();
7180 MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated.");
7181 if (cellMap) {
7182 result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols);
7184 return result;
7187 /* static */
7188 void nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame,
7189 const nsRect& aOrigRect,
7190 const nsRect& aOrigInkOverflow,
7191 bool aIsFirstReflow) {
7192 nsIFrame* parent = aFrame->GetParent();
7193 NS_ASSERTION(parent, "What happened here?");
7195 if (parent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
7196 // Don't bother; we'll invalidate the parent's overflow rect when
7197 // we finish reflowing it.
7198 return;
7201 // The part that looks at both the rect and the overflow rect is a
7202 // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent
7203 // description of its hackishness.
7205 // This doesn't really make sense now that we have DLBI.
7206 // This code can probably be simplified a fair bit.
7207 nsRect inkOverflow = aFrame->InkOverflowRect();
7208 if (aIsFirstReflow || aOrigRect.TopLeft() != aFrame->GetPosition() ||
7209 aOrigInkOverflow.TopLeft() != inkOverflow.TopLeft()) {
7210 // Invalidate the old and new overflow rects. Note that if the
7211 // frame moved, we can't just use aOrigInkOverflow, since it's in
7212 // coordinates relative to the old position. So invalidate via
7213 // aFrame's parent, and reposition that overflow rect to the right
7214 // place.
7215 // XXXbz this doesn't handle outlines, does it?
7216 aFrame->InvalidateFrame();
7217 parent->InvalidateFrameWithRect(aOrigInkOverflow + aOrigRect.TopLeft());
7218 } else if (aOrigRect.Size() != aFrame->GetSize() ||
7219 aOrigInkOverflow.Size() != inkOverflow.Size()) {
7220 aFrame->InvalidateFrameWithRect(aOrigInkOverflow);
7221 aFrame->InvalidateFrame();
7225 void nsTableFrame::AppendDirectlyOwnedAnonBoxes(
7226 nsTArray<OwnedAnonBox>& aResult) {
7227 nsIFrame* wrapper = GetParent();
7228 MOZ_ASSERT(wrapper->Style()->GetPseudoType() == PseudoStyleType::tableWrapper,
7229 "What happened to our parent?");
7230 aResult.AppendElement(
7231 OwnedAnonBox(wrapper, &UpdateStyleOfOwnedAnonBoxesForTableWrapper));
7234 /* static */
7235 void nsTableFrame::UpdateStyleOfOwnedAnonBoxesForTableWrapper(
7236 nsIFrame* aOwningFrame, nsIFrame* aWrapperFrame,
7237 ServoRestyleState& aRestyleState) {
7238 MOZ_ASSERT(
7239 aWrapperFrame->Style()->GetPseudoType() == PseudoStyleType::tableWrapper,
7240 "What happened to our parent?");
7242 RefPtr<ComputedStyle> newStyle =
7243 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
7244 PseudoStyleType::tableWrapper, aOwningFrame->Style());
7246 // Figure out whether we have an actual change. It's important that we do
7247 // this, even though all the wrapper's changes are due to properties it
7248 // inherits from us, because it's possible that no one ever asked us for those
7249 // style structs and hence changes to them aren't reflected in
7250 // the handled changes at all.
7252 // Also note that extensions can add/remove stylesheets that change the styles
7253 // of anonymous boxes directly, so we need to handle that potential change
7254 // here.
7256 // NOTE(emilio): We can't use the ChangesHandledFor optimization (and we
7257 // assert against that), because the table wrapper is up in the frame tree
7258 // compared to the owner frame.
7259 uint32_t equalStructs; // Not used, actually.
7260 nsChangeHint wrapperHint =
7261 aWrapperFrame->Style()->CalcStyleDifference(*newStyle, &equalStructs);
7263 if (wrapperHint) {
7264 aRestyleState.ChangeList().AppendChange(
7265 aWrapperFrame, aWrapperFrame->GetContent(), wrapperHint);
7268 for (nsIFrame* cur = aWrapperFrame; cur; cur = cur->GetNextContinuation()) {
7269 cur->SetComputedStyle(newStyle);
7272 MOZ_ASSERT(!aWrapperFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES),
7273 "Wrapper frame doesn't have any anon boxes of its own!");
7276 namespace mozilla {
7278 nsRect nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder,
7279 bool* aSnap) const {
7280 *aSnap = false;
7281 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
7284 nsDisplayTableBackgroundSet::nsDisplayTableBackgroundSet(
7285 nsDisplayListBuilder* aBuilder, nsIFrame* aTable)
7286 : mBuilder(aBuilder),
7287 mColGroupBackgrounds(aBuilder),
7288 mColBackgrounds(aBuilder),
7289 mCurrentScrollParentId(aBuilder->GetCurrentScrollParentId()) {
7290 mPrevTableBackgroundSet = mBuilder->SetTableBackgroundSet(this);
7291 mozilla::DebugOnly<const nsIFrame*> reference =
7292 mBuilder->FindReferenceFrameFor(aTable, &mToReferenceFrame);
7293 MOZ_ASSERT(nsLayoutUtils::FindNearestCommonAncestorFrame(reference, aTable));
7294 mDirtyRect = mBuilder->GetDirtyRect();
7295 mCombinedTableClipChain =
7296 mBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
7297 mTableASR = mBuilder->CurrentActiveScrolledRoot();
7300 // A display item that draws all collapsed borders for a table.
7301 // At some point, we may want to find a nicer partitioning for dividing
7302 // border-collapse segments into their own display items.
7303 class nsDisplayTableBorderCollapse final : public nsDisplayTableItem {
7304 public:
7305 nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder,
7306 nsTableFrame* aFrame)
7307 : nsDisplayTableItem(aBuilder, aFrame) {
7308 MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse);
7310 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableBorderCollapse)
7312 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
7313 bool CreateWebRenderCommands(
7314 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
7315 const StackingContextHelper& aSc,
7316 layers::RenderRootStateManager* aManager,
7317 nsDisplayListBuilder* aDisplayListBuilder) override;
7318 NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE)
7321 void nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder,
7322 gfxContext* aCtx) {
7323 nsPoint pt = ToReferenceFrame();
7324 DrawTarget* drawTarget = aCtx->GetDrawTarget();
7326 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
7327 pt, mFrame->PresContext()->AppUnitsPerDevPixel());
7329 // XXX we should probably get rid of this translation at some stage
7330 // But that would mean modifying PaintBCBorders, ugh
7331 AutoRestoreTransform autoRestoreTransform(drawTarget);
7332 drawTarget->SetTransform(
7333 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
7335 static_cast<nsTableFrame*>(mFrame)->PaintBCBorders(
7336 *drawTarget, GetPaintRect(aBuilder, aCtx) - pt);
7339 bool nsDisplayTableBorderCollapse::CreateWebRenderCommands(
7340 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
7341 const StackingContextHelper& aSc,
7342 mozilla::layers::RenderRootStateManager* aManager,
7343 nsDisplayListBuilder* aDisplayListBuilder) {
7344 bool dummy;
7345 static_cast<nsTableFrame*>(mFrame)->CreateWebRenderCommandsForBCBorders(
7346 aBuilder, aSc, GetBounds(aDisplayListBuilder, &dummy),
7347 ToReferenceFrame());
7348 return true;
7351 } // namespace mozilla