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"
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"
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"
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
;
70 struct TableReflowInput final
{
71 TableReflowInput(const ReflowInput
& aReflowInput
,
72 const LogicalMargin
& aBorderPadding
, TableReflowMode aMode
)
73 : mReflowInput(aReflowInput
),
74 mWM(aReflowInput
.GetWritingMode()),
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
) {
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.
114 // Running block-offset, which will be adjusted as we reflow children.
118 void ReduceAvailableBSizeBy(nscoord aAmount
) {
119 if (mAvailSize
.BSize(mWM
) == NS_UNCONSTRAINEDSIZE
) {
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.
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 /********************************************************************************
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;
162 return GetParent()->DoGetParentComputedStyle(aProviderFrame
);
165 nsTableFrame::nsTableFrame(ComputedStyle
* aStyle
, nsPresContext
* aPresContext
,
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);
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);
197 mTableLayoutStrategy
= MakeUnique
<FixedTableLayoutStrategy
>(this);
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
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
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
) {
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
);
281 // Add this frame to the list.
282 positionedParts
->AppendElement(aFrame
);
284 positionedParts
->RemoveElement(aFrame
);
289 void nsTableFrame::MaybeUnregisterPositionedTablePart(nsIFrame
* aFrame
) {
290 if (!aFrame
->IsAbsPosContainingBlock()) {
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());
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
));
322 MOZ_ASSERT(mFrames
.IsEmpty() && mColGroups
.IsEmpty(),
323 "unexpected second call to SetInitialChildList");
325 for (nsIFrame
* f
: aChildList
) {
326 MOZ_ASSERT(f
->GetParent() == this, "Unexpected parent");
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
) {
363 nsTableCellMap
* cellMap
= GetCellMap();
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
,
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();
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) {
402 int32_t nsTableFrame::GetIndexOfLastRealCol() {
403 int32_t numCols
= mColFrames
.Length();
405 for (int32_t colIdx
= numCols
- 1; colIdx
>= 0; colIdx
--) {
406 nsTableColFrame
* colFrame
= GetColFrame(colIdx
);
408 if (eColAnonymousCell
!= colFrame
->GetColType()) {
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
);
424 MOZ_ASSERT_UNREACHABLE("invalid col index");
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();
446 return aCellMap
->GetRowSpan(rowIndex
, colIndex
, true);
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();
460 return aCellMap
->GetEffectiveColSpan(*tableCellMap
, rowIndex
, colIndex
);
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
));
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
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");
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
);
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();
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();
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);
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();
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
,
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();
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);
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);
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()
662 colGroupFrame
= CreateSyntheticColGroupFrame();
663 if (!colGroupFrame
) {
666 // add the new frame to the child list
667 mColGroups
.AppendFrame(this, colGroupFrame
);
668 colGroupFrame
->SetStartColumnIndex(colIndex
);
670 AppendAnonymousColFrames(colGroupFrame
, aNumColsToAdd
, eColAnonymousCell
,
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
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
));
713 // get the starting col index in the cache
714 int32_t startColIndex
;
717 static_cast<nsTableColFrame
*>(oldLastCol
)->GetColIndex() + 1;
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();
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();
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);
798 return (aNumFrames
- numColsRemoved
);
801 void nsTableFrame::RemoveCell(nsTableCellFrame
* aCellFrame
, int32_t aRowIndex
) {
802 nsTableCellMap
* cellMap
= GetCellMap();
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
) {
823 int32_t numRows
= rgFrame
->GetRowCount();
829 // this cannot extend beyond a single row group
830 void nsTableFrame::AppendRows(nsTableRowGroupFrame
* aRowGroupFrame
,
832 nsTArray
<nsTableRowFrame
*>& aRowFrames
) {
833 nsTableCellMap
* cellMap
= GetCellMap();
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);
849 int32_t numColsToAdd
= 0;
850 nsTableCellMap
* cellMap
= GetCellMap();
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
,
861 MatchCellMapToColCache(cellMap
);
863 // Perform row index adjustment only if row indices were not
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);
891 void nsTableFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex
) {
892 if (mDeletedRowIndexRanges
.empty()) {
893 mDeletedRowIndexRanges
.insert(std::pair
<int32_t, int32_t>(
894 aDeletedRowStoredIndex
, aDeletedRowStoredIndex
));
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()) {
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
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
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
940 smallerIter
->second
= greaterIter
->second
;
941 mDeletedRowIndexRanges
.erase(greaterIter
);
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
);
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;
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
980 bool stopTelling
= false;
981 for (nsIFrame
* kidFrame
= aFirstFrame
.FirstChild(); (kidFrame
&& !stopAsking
);
982 kidFrame
= kidFrame
->GetNextSibling()) {
983 nsTableCellFrame
* cellFrame
= do_QueryFrame(kidFrame
);
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
995 int32_t firstRowIndex
= aFirstRowFrame
.GetRowIndex();
996 #ifdef DEBUG_TABLE_CELLMAP
997 printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex
,
999 Dump(true, false, true);
1001 nsTableCellMap
* cellMap
= GetCellMap();
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
,
1012 MatchCellMapToColCache(cellMap
);
1013 if (IsBorderCollapse()) {
1014 AddBCDamageArea(damageArea
);
1018 #ifdef DEBUG_TABLE_CELLMAP
1019 printf("=== removeRowsAfter\n");
1020 Dump(true, true, true);
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
));
1036 void nsTableFrame::InsertRowGroups(const nsFrameList::Slice
& aRowGroups
) {
1037 #ifdef DEBUG_TABLE_CELLMAP
1038 printf("=== insertRowGroupsBefore\n");
1039 Dump(true, false, true);
1041 nsTableCellMap
* cellMap
= GetCellMap();
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!
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
);
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
);
1075 int32_t rowIndex
= 0;
1077 int32_t priorNumRows
= priorRG
->GetRowCount();
1078 rowIndex
= priorRG
->GetStartRowIndex() + priorNumRows
;
1080 InsertRows(orderedRowGroups
[rgIndex
], rows
, rowIndex
, true);
1088 #ifdef DEBUG_TABLE_CELLMAP
1089 printf("=== insertRowGroupsAfter\n");
1090 Dump(true, true, true);
1094 /////////////////////////////////////////////////////////////////////////////
1095 // Child frame enumeration
1097 const nsFrameList
& nsTableFrame::GetChildList(ChildListID aListID
) const {
1098 if (aListID
== FrameChildListID::ColGroup
) {
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()) {
1114 return f
->StyleBorder()->HasBorder();
1117 void nsTableFrame::CalcHasBCBorders() {
1118 if (!IsBorderCollapse()) {
1119 SetHasBCBorders(false);
1123 if (FrameHasBorder(this)) {
1124 SetHasBCBorders(true);
1128 // Check col and col group has borders.
1129 for (nsIFrame
* f
: this->GetChildList(FrameChildListID::ColGroup
)) {
1130 if (FrameHasBorder(f
)) {
1131 SetHasBCBorders(true);
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);
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);
1153 for (nsTableRowFrame
* row
= rowGroup
->GetFirstRow(); row
;
1154 row
= row
->GetNextRow()) {
1155 if (FrameHasBorder(row
)) {
1156 SetHasBCBorders(true);
1160 for (nsTableCellFrame
* cell
= row
->GetFirstCell(); cell
;
1161 cell
= cell
->GetNextCell()) {
1162 if (FrameHasBorder(cell
)) {
1163 SetHasBCBorders(true);
1170 SetHasBCBorders(false);
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
>(
1219 const nsStyleBorder
* borderStyle
= StyleBorder();
1220 if (borderStyle
->HasBorder()) {
1221 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayBorder
>(aBuilder
,
1228 LogicalSides
nsTableFrame::GetLogicalSkipSides() const {
1229 LogicalSides
skip(mWritingMode
);
1230 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak
==
1231 StyleBoxDecorationBreak::Clone
)) {
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
;
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()));
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
);
1270 fif
->GetColumnISizeFromFirstInFlow(colIdx
) + colSpacing
;
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
,
1292 colFrame
->SetRect(aWM
, colRect
, colGroupSize
);
1293 colSpacing
= GetColSpacing(colIdx
);
1294 colOrigin
.I(aWM
) += colISize
+ colSpacing
;
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
);
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
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.
1344 tls
->MarkIntrinsicISizesDirty();
1346 // XXXldb Call SetBCDamageArea?
1348 nsContainerFrame::MarkIntrinsicISizesDirty();
1352 nscoord
nsTableFrame::GetMinISize(gfxContext
* aRenderingContext
) {
1353 if (NeedToCalcBCBorders()) CalcBCBorders();
1355 ReflowColGroups(aRenderingContext
);
1357 return LayoutStrategy()->GetMinISize(aRenderingContext
);
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
);
1376 if (IsBorderCollapse()) {
1379 WritingMode wm
= GetWritingMode();
1380 LogicalMargin outerBC
= GetIncludedOuterBCBorder(wm
);
1381 result
.border
= outerBC
.IStartEnd(wm
);
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()) {
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
;
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);
1429 nscoord minISize
= GetMinISize(aRenderingContext
);
1430 if (minISize
> aISizeInCB
) {
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
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
;
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.
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()) {
1480 } else if (LayoutFrameType::Table
== frameType
) {
1481 // we reached the containing table, so always return
1482 return !rs
->mStylePosition
->BSize(wm
).IsAuto();
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
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
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
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
) {
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");
1634 // see if collapsing borders need to be calculated
1635 if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) {
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() ||
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.
1686 bool needToInitiateSpecialReflow
= false;
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;
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
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
) {
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
,
1927 MOZ_ASSERT(aPushFrom
> 0, "pushing first child");
1929 // Extract the frames from the array into a frame list.
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()) {
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
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");
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
) {
2003 if (ColumnHasCellSpacingBefore(colIdx
)) {
2004 iSize
+= GetColSpacing(colIdx
- 1);
2007 SetNeedToCollapse(true);
2016 void nsTableFrame::DidSetComputedStyle(ComputedStyle
* aOldComputedStyle
) {
2017 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle
);
2019 if (!aOldComputedStyle
) // avoid this on init
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
)) {
2032 mTableLayoutStrategy
= MakeUnique
<BasicTableLayoutStrategy
>(this);
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
));
2061 nsTableColGroupFrame
* lastColGroup
=
2062 nsTableColGroupFrame::GetLastRealColGroup(this);
2063 int32_t startColIndex
= (lastColGroup
)
2064 ? lastColGroup
->GetStartColumnIndex() +
2065 lastColGroup
->GetColCount()
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));
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);
2090 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
2091 NS_FRAME_HAS_DIRTY_CHILDREN
);
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
));
2114 // Collect ColGroupFrames into a separate list and insert those separately
2115 // from the other frames (bug 759249).
2116 nsFrameList colGroupList
;
2117 nsFrameList principalList
;
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
));
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
,
2137 if (principalList
.NotEmpty()) {
2138 HomogenousInsertFrames(FrameChildListID::Principal
, aPrevFrame
,
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();
2149 mozilla::StyleDisplay::TableColumnGroup
== display
->mDisplay
;
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
;
2156 isColGroup
== (nextDisplay
== mozilla::StyleDisplay::TableColumnGroup
),
2157 "heterogenous childlist");
2160 if (MOZ_UNLIKELY(isColGroup
&& GetPrevInFlow())) {
2161 auto firstInFlow
= static_cast<nsTableFrame
*>(FirstInFlow());
2162 firstInFlow
->AppendFrames(aListID
, std::move(aFrameList
));
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
);
2185 nsTableColGroupFrame
* lastColGroup
= nullptr;
2187 kidFrame
= mColGroups
.FirstChild();
2188 lastColGroup
= nsTableColGroupFrame::GetLastRealColGroup(this);
2190 kidFrame
= mFrames
.FirstChild();
2192 // Important: need to start at a value smaller than all valid indices
2193 Maybe
<uint32_t> lastIndex
;
2196 if (kidFrame
== lastColGroup
) {
2198 kidFrame
; // there is no real colgroup after this one
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
2210 if ((index
.isSome() &&
2211 (lastIndex
.isNothing() || *index
> *lastIndex
)) &&
2212 (newIndex
.isSome() &&
2213 (index
.isNothing() || *index
< *newIndex
))) {
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;
2231 nsTableColGroupFrame
* prevColGroup
=
2232 (nsTableColGroupFrame
*)GetFrameAtOrBefore(
2233 this, aPrevFrame
, LayoutFrameType::TableColGroup
);
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
);
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
));
2258 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
2259 NS_FRAME_HAS_DIRTY_CHILDREN
);
2261 #ifdef DEBUG_TABLE_CELLMAP
2262 printf("=== TableFrame::InsertFrames\n");
2263 Dump(true, true, true);
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
2278 for (colIdx
= lastColIndex
; colIdx
>= firstColIndex
; colIdx
--) {
2279 nsTableColFrame
* colFrame
= mColFrames
.SafeElementAt(colIdx
);
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
);
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
);
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();
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
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,
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;
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);
2375 nsMargin
nsTableFrame::GetUsedBorder() const {
2376 if (!IsBorderCollapse()) return nsContainerFrame::GetUsedBorder();
2378 WritingMode wm
= GetWritingMode();
2379 return GetIncludedOuterBCBorder(wm
).GetPhysicalMargin(wm
);
2383 nsMargin
nsTableFrame::GetUsedPadding() const {
2384 if (!IsBorderCollapse()) return nsContainerFrame::GetUsedPadding();
2386 return nsMargin(0, 0, 0, 0);
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
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());
2407 value
= new TableBCData();
2408 SetProperty(TableBCDataProperty(), value
);
2411 MOZ_ASSERT(value
, "TableBCData must exist!");
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();
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();
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
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
,
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();
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
);
2533 case StyleDisplayInside::TableFooterGroup
:
2534 if (foot
) { // treat additional tfoot like tbody
2535 children
.AppendElement(rowGroup
);
2540 case StyleDisplayInside::TableRowGroup
:
2541 children
.AppendElement(rowGroup
);
2544 MOZ_ASSERT_UNREACHABLE("How did this produce an nsTableRowGroupFrame?");
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.
2551 nsIFrame
* nif
= kidFrame
->GetNextInFlow();
2552 kidFrame
= kidFrame
->GetNextSibling();
2553 if (kidFrame
!= nif
) {
2559 // put the thead first
2561 children
.InsertElementAt(0, head
);
2566 // put the tfoot after the last tbody
2568 children
.AppendElement(foot
);
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
,
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
) {
2645 aLastChildReflowed
= nullptr;
2647 nsIFrame
* prevKidFrame
= nullptr;
2648 WritingMode wm
= aReflowInput
.mReflowInput
.GetWritingMode();
2649 NS_WARNING_ASSERTION(
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.)
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()) {
2681 aOverflowAreas
.Clear();
2683 bool reflowAllKids
= aReflowInput
.mReflowInput
.ShouldReflowAllKids() ||
2684 mBits
.mResizedColumns
|| IsGeometryDirty() ||
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.
2702 bool reorder
= false;
2703 if (thead
&& !GetPrevInFlow()) {
2704 reorder
= thead
->GetNextInFlow();
2705 SetupHeaderFooterChild(aReflowInput
, thead
);
2708 reorder
= reorder
|| tfoot
->GetNextInFlow();
2709 footerBSize
= SetupHeaderFooterChild(aReflowInput
, tfoot
);
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
&&
2725 kidFrame
->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
)))) {
2726 // A helper to place a repeated footer if allowed, or set it as
2728 auto MaybePlaceRepeatedFooter
= [&]() {
2729 if (allowRepeatedFooter
) {
2730 PlaceRepeatedFooter(aReflowInput
, tfoot
, footerBSize
);
2731 } else if (tfoot
&& tfoot
->IsRepeatable()) {
2732 tfoot
->SetRepeatable(false);
2737 MaybePlaceRepeatedFooter();
2738 PushChildrenToOverflow(rowGroups
, childX
);
2740 aStatus
.SetIncomplete();
2741 aLastChildReflowed
= allowRepeatedFooter
? tfoot
: prevKidFrame
;
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],
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
)
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
);
2791 ReflowChild(kidFrame
, presContext
, desiredSize
, kidReflowInput
, wm
,
2792 kidPosition
, containerSize
, ReflowChildFlags::Default
,
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();
2807 // see if the rowgroup did not fit on this page might be pushed on
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();
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
,
2824 MaybePlaceRepeatedFooter();
2826 aStatus
.SetIncomplete();
2827 PushChildrenToOverflow(rowGroups
, childX
+ 1);
2828 aLastChildReflowed
= allowRepeatedFooter
? tfoot
: kidFrame
;
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();
2835 aStatus
.SetIncomplete();
2836 PushChildrenToOverflow(rowGroups
, childX
);
2837 aLastChildReflowed
= allowRepeatedFooter
? tfoot
: prevKidFrame
;
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
,
2843 MaybePlaceRepeatedFooter();
2844 aLastChildReflowed
= allowRepeatedFooter
? tfoot
: kidFrame
;
2850 aLastChildReflowed
= kidFrame
;
2853 // see if there is a page break after this row group or before the next
2855 if (aStatus
.IsComplete() && isPaginated
&&
2856 (kidReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
)) {
2858 (childX
+ 1 < rowGroups
.Length()) ? rowGroups
[childX
+ 1] : nullptr;
2859 pageBreak
= PageBreakAfter(kidFrame
, nextKid
);
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
2880 PresShell()->FrameConstructor()->CreateContinuingFrame(kidFrame
,
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
2899 MaybePlaceRepeatedFooter();
2900 if (kidFrame
->GetNextSibling()) {
2901 PushChildrenToOverflow(rowGroups
, childX
+ 1);
2903 aLastChildReflowed
= allowRepeatedFooter
? tfoot
: kidFrame
;
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
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 {
2945 auto* nextInFlow
= static_cast<nsTableFrame
*>(GetNextInFlow());
2949 for (nsIFrame
* kidFrame
: nextInFlow
->mFrames
) {
2950 if (!IsRepeatedFrame(kidFrame
)) {
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
,
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
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()) {
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()) {
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
);
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();
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
,
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
3085 aReflowInput
.ComputedBSize() - GetRowSpacing(-1, GetRowCount());
3086 nscoord bOriginRG
= borderPadding
.BStart(wm
) + GetRowSpacing(0);
3087 nscoord bEndRG
= bOriginRG
;
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();
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
;
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();
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();
3167 if (amountUsed
>= aAmount
) {
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();
3181 if (!rowFrame
->HasStyleBSize()) {
3182 firstUnStyledRG
= rgFrame
;
3183 firstUnStyledRow
= rowFrame
;
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();
3204 for (rgIdx
= 0; rgIdx
< rowGroups
.Length(); rgIdx
++) {
3205 nsTableRowGroupFrame
* rgFrame
= rowGroups
[rgIdx
];
3206 if (!firstUnStyledRG
|| !rgFrame
->HasStyleBSize()) {
3207 nsTableRowFrame
* rowFrame
= rgFrame
->GetFirstRow();
3209 if (!firstUnStyledRG
|| !rowFrame
->HasStyleBSize()) {
3210 NS_ASSERTION(rowFrame
->BSize(wm
) >= 0,
3211 "negative row frame block-size");
3212 divisor
+= rowFrame
->BSize(wm
);
3214 lastEligibleRow
= rowFrame
;
3216 rowFrame
= rowFrame
->GetNextRow();
3221 if (eligibleRows
> 0) {
3222 expandEmptyRows
= true;
3224 NS_ERROR("invalid divisor");
3229 // allocate the extra bsize to the unstyled row groups and rows
3230 nscoord bSizeToDistribute
= aAmount
- amountUsed
;
3231 bOriginRG
= borderPadding
.BStart(wm
) + GetRowSpacing(-1);
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
) {
3256 if (!expandEmptyRows
) {
3257 // The amount of additional space each row gets is proportional
3259 ratio
= float(rowNormalRect
.BSize(wm
)) / float(divisor
);
3261 // empty rows get all the same additional space
3262 ratio
= 1.0f
/ float(eligibleRows
);
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
)));
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);
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();
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
) {
3431 Maybe
<nscoord
> nsTableFrame::GetNaturalBaselineBOffset(
3432 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
3433 BaselineExportContext
) const {
3434 if (StyleDisplay()->IsContainLayout()) {
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();
3458 return TableBaseline(rgFrame
, row
);
3462 for (uint32_t rgIndex
= orderedRowGroups
.Length(); rgIndex
-- > 0;) {
3463 nsTableRowGroupFrame
* rgFrame
= orderedRowGroups
[rgIndex
];
3464 nsTableRowFrame
* row
= rgFrame
->GetLastRow();
3466 return TableBaseline(rgFrame
, row
).map([this, aWM
](nscoord aBaseline
) {
3467 return BSize(aWM
) - aBaseline
;
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");
3494 bool nsTableFrame::IsAutoBSize(WritingMode aWM
) {
3495 const auto& bsize
= StylePosition()->BSize(aWM
);
3496 if (bsize
.IsAuto()) {
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
);
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
) {
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
;
3560 void nsTableFrame::DumpRowGroup(nsIFrame
* aKidFrame
) {
3561 if (!aKidFrame
) return;
3563 for (nsIFrame
* cFrame
: aKidFrame
->PrincipalChildList()) {
3564 nsTableRowFrame
* rowFrame
= do_QueryFrame(cFrame
);
3566 printf("row(%d)=%p ", rowFrame
->GetRowIndex(),
3567 static_cast<void*>(rowFrame
));
3568 for (nsIFrame
* childFrame
: cFrame
->PrincipalChildList()) {
3569 nsTableCellFrame
* cellFrame
= do_QueryFrame(childFrame
);
3571 uint32_t colIndex
= cellFrame
->ColIndex();
3572 printf("cell(%u)=%p ", colIndex
, static_cast<void*>(childFrame
));
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();
3588 nsTableFrame
* fif
= static_cast<nsTableFrame
*>(FirstInFlow());
3589 for (colIdx
= 0; colIdx
< numCols
; colIdx
++) {
3590 printf("%d ", fif
->GetColumnISizeFromFirstInFlow(colIdx
));
3595 nsIFrame
* kidFrame
= mFrames
.FirstChild();
3597 DumpRowGroup(kidFrame
);
3598 kidFrame
= kidFrame
->GetNextSibling();
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)) {
3610 printf("%d=%p ", colIdx
, static_cast<void*>(colFrame
));
3611 nsTableColType colType
= colFrame
->GetColType();
3614 printf(" content ");
3616 case eColAnonymousCol
:
3617 printf(" anonymous-column ");
3619 case eColAnonymousColGroup
:
3620 printf(" anonymous-colgroup ");
3622 case eColAnonymousCell
:
3623 printf(" anonymous-cell ");
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
++) {
3636 nsTableColFrame
* colFrame
= GetColFrame(colIdx
);
3641 nsTableCellMap
* cellMap
= GetCellMap();
3644 printf(" ***END TABLE DUMP*** \n");
3648 bool nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex
) const {
3649 if (aColIndex
== 0) {
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
) {
3658 nsTableCellMap
* cellMap
= fif
->GetCellMap();
3662 if (cellMap
->GetNumCellsOriginatingInCol(aColIndex
) > 0) {
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) {
3675 const auto& minISize
= col
->StylePosition()->MinISize(GetWritingMode());
3676 if (minISize
.ConvertsToLength() && minISize
.ToLength() > 0) {
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 *******************************************************************************/
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");
3710 void nsTableFrame::AddBCDamageArea(const TableArea
& aValue
) {
3711 MOZ_ASSERT(IsBorderCollapse(),
3712 "Why call this if we are not border-collapsed?");
3714 VerifyDamageRect(aValue
);
3717 SetNeedToCalcBCBorders(true);
3718 SetNeedToCalcHasBCBorders(true);
3720 TableBCData
* value
= GetOrCreateTableBCData();
3723 VerifyNonNegativeDamageRect(value
->mDamageArea
);
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;
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;
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
3790 void BCCellBorder::Reset(uint32_t aRowIndex
, uint32_t aRowSpan
) {
3791 style
= StyleBorderStyle::None
;
3794 owner
= eTableOwner
;
3795 rowIndex
= aRowIndex
;
3799 class BCMapCellIterator
;
3801 /*****************************************************************
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
;
3874 BCCellData
* mCellData
;
3875 nsBCTableCellFrame
* mCell
;
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
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) {
3904 void BCMapCellInfo::ResetCellInfo() {
3905 mCellData
= nullptr;
3906 mRowGroup
= nullptr;
3907 mStartRow
= nullptr;
3909 mColGroup
= nullptr;
3910 mStartCol
= 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
{
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
;
3951 nsCellMap
* mCellMap
;
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
;
3969 uint32_t mNumTableCols
;
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.
3978 BCMapCellIterator::BCMapCellIterator(nsTableFrame
* aTableFrame
,
3979 const TableArea
& aDamageArea
)
3980 : mRowGroupStart(0),
3983 mTableFrame(aTableFrame
),
3984 mRowGroups(aTableFrame
->OrderedRowGroups()),
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();
3998 mNumTableCols
= mTableFrame
->GetColCount();
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
4017 mStartRow
= aNewRow
;
4018 mRowIndex
= aNewRow
->GetRowIndex();
4021 // fill cell frame info and row information
4026 mCell
= static_cast<nsBCTableCellFrame
*>(aCellData
->GetCellFrame());
4029 mStartRow
= mCell
->GetTableRowFrame();
4030 if (!mStartRow
) ABORT0();
4031 mRowIndex
= mStartRow
->GetRowIndex();
4033 mColSpan
= mTableFrame
->GetEffectiveColSpan(*mCell
, aCellMap
);
4034 mRowSpan
= mTableFrame
->GetEffectiveRowSpan(*mCell
, aCellMap
);
4039 mStartRow
= aIter
->GetCurrentRow();
4041 if (1 == mRowSpan
) {
4042 mEndRow
= mStartRow
;
4044 mEndRow
= mStartRow
->GetNextRow();
4046 for (int32_t span
= 2; mEndRow
&& span
< mRowSpan
; span
++) {
4047 mEndRow
= mEndRow
->GetNextRow();
4049 NS_ASSERTION(mEndRow
, "spanned row not found");
4051 NS_ERROR("error in cell map");
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
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;
4072 mStartCol
= mTableFirstInFlow
->GetColFrame(aColIndex
);
4073 if (!mStartCol
) ABORT0();
4075 mEndCol
= mStartCol
;
4077 nsTableColFrame
* colFrame
=
4078 mTableFirstInFlow
->GetColFrame(aColIndex
+ mColSpan
- 1);
4079 if (!colFrame
) ABORT0();
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
) {
4097 mRow
= mRow
->GetNextRow();
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())) {
4126 bool BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow
) {
4128 int32_t numRowGroups
= mRowGroups
.Length();
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;
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);
4155 if (SetNewRow(firstRow
)) { // sets mAtEnd
4164 void BCMapCellIterator::First(BCMapCellInfo
& aMapInfo
) {
4165 aMapInfo
.ResetCellInfo();
4167 SetNewRowGroup(true); // sets 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);
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();
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);
4206 if (mRowIndex
>= mRowGroupEnd
) {
4207 SetNewRowGroup(false); // could set mAtEnd
4209 SetNewRow(); // could set mAtEnd
4215 void BCMapCellIterator::PeekIEnd(const BCMapCellInfo
& aRefInfo
,
4216 int32_t aRowIndex
, BCMapCellInfo
& aAjaInfo
) {
4217 PeekIAt(aRefInfo
, aRowIndex
, aRefInfo
.mColIndex
+ aRefInfo
.mColSpan
,
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
;
4233 rg
= mRowGroups
.SafeElementAt(nextRgIndex
);
4235 cellMap
= mTableCellMap
->GetMapFor(rg
, cellMap
);
4236 if (!cellMap
) ABORT0();
4237 // First row of the next row group
4239 nextRow
= rg
->GetFirstRow();
4241 } while (rg
&& !nextRow
);
4244 // get the row within the same row group
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());
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
;
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());
4294 static_cast<BCCellData
*>(mCellMap
->GetDataAt(rgRowIndex
, aColIndex
));
4295 if (!cellData
) ABORT0();
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
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
)) {
4333 *aColor
= aFrame
->Style()->GetVisitedDependentColor(
4334 nsStyleBorder::BorderColorFieldFor(physicalSide
));
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
,
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
{
4362 explicit nsDelayedCalcBCBorders(nsIFrame
* aFrame
)
4363 : mozilla::Runnable("nsDelayedCalcBCBorders"), mFrame(aFrame
) {}
4365 NS_IMETHOD
Run() override
{
4367 nsTableFrame
* tableFrame
= static_cast<nsTableFrame
*>(mFrame
.GetFrame());
4368 if (tableFrame
->NeedToCalcBCBorders()) {
4369 tableFrame
->CalcBCBorders();
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
);
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
;
4439 /** calc the dominant border by considering the table, row/col group, row/col,
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
4468 GetColorAndStyle(aTableFrame
, aTableWM
, aSide
, &border
.style
, &border
.color
,
4470 border
.owner
= eTableOwner
;
4471 if (StyleBorderStyle::Hidden
== border
.style
) {
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
) {
4487 // see if the col is dominant
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
) {
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
) {
4507 // see if the row is dominant
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
) {
4517 // see if the cell is dominant
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);
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
{
4538 ownerWidth
= subWidth
= ownerElem
= subSide
= subElem
= hasDashDot
=
4539 numSegs
= bevel
= 0;
4540 ownerSide
= static_cast<uint16_t>(LogicalSide::BStart
);
4541 ownerStyle
= BORDER_STYLE_UNSET
;
4542 subStyle
= StyleBorderStyle::Solid
;
4545 void Set(mozilla::LogicalSide aSide
, BCCellBorder border
);
4547 void Update(mozilla::LogicalSide aSide
, BCCellBorder border
);
4549 nscolor ownerColor
; // color of borderOwner
4550 uint16_t ownerWidth
; // pixel width of borderOwner
4551 uint16_t subWidth
; // pixel width of the largest border intersecting the
4552 // border perpendicular to ownerSide
4553 StyleBorderStyle subStyle
; // border style of subElem
4554 StyleBorderStyle ownerStyle
; // border style of ownerElem
4555 uint16_t ownerSide
: 2; // LogicalSide (e.g LogicalSide::BStart, etc) of the
4556 // border owning the corner relative to the corner
4558 ownerElem
: 4; // elem type (e.g. eTable, eGroup, etc) owning the corner
4559 uint16_t subSide
: 2; // side of border with subWidth relative to the corner
4560 uint16_t subElem
: 4; // elem type (e.g. eTable, eGroup, etc) of sub owner
4561 uint16_t hasDashDot
: 1; // does a dashed, dotted segment enter the corner,
4562 // they cannot be beveled
4563 uint16_t numSegs
: 3; // number of segments entering corner
4564 uint16_t bevel
: 1; // is the corner beveled (uses the above two fields
4565 // together with subWidth)
4566 // 7 bits are unused
4569 // Start a new border at this corner, going in the direction of a given side.
4570 void BCCornerInfo::Set(mozilla::LogicalSide aSide
, BCCellBorder aBorder
) {
4571 // FIXME bug 1508921: We mask 4-bit BCBorderOwner enum to 3 bits to preserve
4572 // buggy behavior found by the frame_above_rules_all.html mochitest.
4573 ownerElem
= aBorder
.owner
& 0x7;
4575 ownerStyle
= aBorder
.style
;
4576 ownerWidth
= aBorder
.width
;
4577 ownerColor
= aBorder
.color
;
4578 ownerSide
= static_cast<uint16_t>(aSide
);
4581 if (aBorder
.width
> 0) {
4583 hasDashDot
= (StyleBorderStyle::Dashed
== aBorder
.style
) ||
4584 (StyleBorderStyle::Dotted
== aBorder
.style
);
4588 // the following will get set later
4589 subSide
= static_cast<uint16_t>(IsInline(aSide
) ? LogicalSide::BStart
4590 : LogicalSide::IStart
);
4591 subElem
= eTableOwner
;
4592 subStyle
= StyleBorderStyle::Solid
;
4595 // Add a new border going in the direction of a given side, and update the
4597 void BCCornerInfo::Update(mozilla::LogicalSide aSide
, BCCellBorder aBorder
) {
4598 if (ownerStyle
== BORDER_STYLE_UNSET
) {
4599 Set(aSide
, aBorder
);
4601 bool isInline
= IsInline(aSide
); // relative to the corner
4602 BCCellBorder oldBorder
, tempBorder
;
4603 oldBorder
.owner
= (BCBorderOwner
)ownerElem
;
4604 oldBorder
.style
= ownerStyle
;
4605 oldBorder
.width
= ownerWidth
;
4606 oldBorder
.color
= ownerColor
;
4608 LogicalSide oldSide
= LogicalSide(ownerSide
);
4610 bool existingWins
= false;
4611 tempBorder
= CompareBorders(CELL_CORNER
, oldBorder
, aBorder
, isInline
,
4614 ownerElem
= tempBorder
.owner
;
4615 ownerStyle
= tempBorder
.style
;
4616 ownerWidth
= tempBorder
.width
;
4617 ownerColor
= tempBorder
.color
;
4618 if (existingWins
) { // existing corner is dominant
4619 if (::Perpendicular(LogicalSide(ownerSide
), aSide
)) {
4620 // see if the new sub info replaces the old
4621 BCCellBorder subBorder
;
4622 subBorder
.owner
= (BCBorderOwner
)subElem
;
4623 subBorder
.style
= subStyle
;
4624 subBorder
.width
= subWidth
;
4625 subBorder
.color
= 0; // we are not interested in subBorder color
4628 tempBorder
= CompareBorders(CELL_CORNER
, subBorder
, aBorder
, isInline
,
4631 subElem
= tempBorder
.owner
;
4632 subStyle
= tempBorder
.style
;
4633 subWidth
= tempBorder
.width
;
4635 subSide
= static_cast<uint16_t>(aSide
);
4638 } else { // input args are dominant
4639 ownerSide
= static_cast<uint16_t>(aSide
);
4640 if (::Perpendicular(oldSide
, LogicalSide(ownerSide
))) {
4641 subElem
= oldBorder
.owner
;
4642 subStyle
= oldBorder
.style
;
4643 subWidth
= oldBorder
.width
;
4644 subSide
= static_cast<uint16_t>(oldSide
);
4647 if (aBorder
.width
> 0) {
4649 if (!hasDashDot
&& ((StyleBorderStyle::Dashed
== aBorder
.style
) ||
4650 (StyleBorderStyle::Dotted
== aBorder
.style
))) {
4655 // bevel the corner if only two perpendicular non dashed/dotted segments
4657 bevel
= (2 == numSegs
) && (subWidth
> 1) && (0 == hasDashDot
);
4662 BCCorners(int32_t aNumCorners
, int32_t aStartIndex
);
4664 BCCornerInfo
& operator[](int32_t i
) const {
4665 NS_ASSERTION((i
>= startIndex
) && (i
<= endIndex
), "program error");
4666 return corners
[clamped(i
, startIndex
, endIndex
) - startIndex
];
4671 UniquePtr
<BCCornerInfo
[]> corners
;
4674 BCCorners::BCCorners(int32_t aNumCorners
, int32_t aStartIndex
) {
4675 NS_ASSERTION((aNumCorners
> 0) && (aStartIndex
>= 0), "program error");
4676 startIndex
= aStartIndex
;
4677 endIndex
= aStartIndex
+ aNumCorners
- 1;
4678 corners
= MakeUnique
<BCCornerInfo
[]>(aNumCorners
);
4681 struct BCCellBorders
{
4682 BCCellBorders(int32_t aNumBorders
, int32_t aStartIndex
);
4684 BCCellBorder
& operator[](int32_t i
) const {
4685 NS_ASSERTION((i
>= startIndex
) && (i
<= endIndex
), "program error");
4686 return borders
[clamped(i
, startIndex
, endIndex
) - startIndex
];
4691 UniquePtr
<BCCellBorder
[]> borders
;
4694 BCCellBorders::BCCellBorders(int32_t aNumBorders
, int32_t aStartIndex
) {
4695 NS_ASSERTION((aNumBorders
> 0) && (aStartIndex
>= 0), "program error");
4696 startIndex
= aStartIndex
;
4697 endIndex
= aStartIndex
+ aNumBorders
- 1;
4698 borders
= MakeUnique
<BCCellBorder
[]>(aNumBorders
);
4701 // this function sets the new border properties and returns true if the border
4702 // segment will start a new segment and not be accumulated into the previous
4704 static bool SetBorder(const BCCellBorder
& aNewBorder
, BCCellBorder
& aBorder
) {
4705 bool changed
= (aNewBorder
.style
!= aBorder
.style
) ||
4706 (aNewBorder
.width
!= aBorder
.width
) ||
4707 (aNewBorder
.color
!= aBorder
.color
);
4708 aBorder
.color
= aNewBorder
.color
;
4709 aBorder
.width
= aNewBorder
.width
;
4710 aBorder
.style
= aNewBorder
.style
;
4711 aBorder
.owner
= aNewBorder
.owner
;
4716 // this function will set the inline-dir border. It will return true if the
4717 // existing segment will not be continued. Having a block-dir owner of a corner
4718 // should also start a new segment.
4719 static bool SetInlineDirBorder(const BCCellBorder
& aNewBorder
,
4720 const BCCornerInfo
& aCorner
,
4721 BCCellBorder
& aBorder
) {
4722 bool startSeg
= ::SetBorder(aNewBorder
, aBorder
);
4724 startSeg
= !IsInline(LogicalSide(aCorner
.ownerSide
));
4729 // Make the damage area larger on the top and bottom by at least one row and on
4730 // the left and right at least one column. This is done so that adjacent
4731 // elements are part of the border calculations. The extra segments and borders
4732 // outside the actual damage area will not be updated in the cell map, because
4733 // they in turn would need info from adjacent segments outside the damage area
4735 void nsTableFrame::ExpandBCDamageArea(TableArea
& aArea
) const {
4736 int32_t numRows
= GetRowCount();
4737 int32_t numCols
= GetColCount();
4739 int32_t dStartX
= aArea
.StartCol();
4740 int32_t dEndX
= aArea
.EndCol() - 1;
4741 int32_t dStartY
= aArea
.StartRow();
4742 int32_t dEndY
= aArea
.EndRow() - 1;
4744 // expand the damage area in each direction
4748 if (dEndX
< (numCols
- 1)) {
4754 if (dEndY
< (numRows
- 1)) {
4757 // Check the damage area so that there are no cells spanning in or out. If
4758 // there are any then make the damage area as big as the table, similarly to
4759 // the way the cell map decides whether to rebuild versus expand. This could
4760 // be optimized to expand to the smallest area that contains no spanners, but
4761 // it may not be worth the effort in general, and it would need to be done in
4762 // the cell map as well.
4763 bool haveSpanner
= false;
4764 if ((dStartX
> 0) || (dEndX
< (numCols
- 1)) || (dStartY
> 0) ||
4765 (dEndY
< (numRows
- 1))) {
4766 nsTableCellMap
* tableCellMap
= GetCellMap();
4767 if (!tableCellMap
) ABORT0();
4768 // Get the ordered row groups
4769 RowGroupArray rowGroups
= OrderedRowGroups();
4771 // Scope outside loop to be used as hint.
4772 nsCellMap
* cellMap
= nullptr;
4773 for (uint32_t rgIdx
= 0; rgIdx
< rowGroups
.Length(); rgIdx
++) {
4774 nsTableRowGroupFrame
* rgFrame
= rowGroups
[rgIdx
];
4775 int32_t rgStartY
= rgFrame
->GetStartRowIndex();
4776 int32_t rgEndY
= rgStartY
+ rgFrame
->GetRowCount() - 1;
4777 if (dEndY
< rgStartY
) break;
4778 cellMap
= tableCellMap
->GetMapFor(rgFrame
, cellMap
);
4779 if (!cellMap
) ABORT0();
4780 // check for spanners from above and below
4781 if ((dStartY
> 0) && (dStartY
>= rgStartY
) && (dStartY
<= rgEndY
)) {
4782 if (uint32_t(dStartY
- rgStartY
) >= cellMap
->mRows
.Length()) ABORT0();
4783 const nsCellMap::CellDataArray
& row
=
4784 cellMap
->mRows
[dStartY
- rgStartY
];
4785 for (int32_t x
= dStartX
; x
<= dEndX
; x
++) {
4786 CellData
* cellData
= row
.SafeElementAt(x
);
4787 if (cellData
&& (cellData
->IsRowSpan())) {
4792 if (dEndY
< rgEndY
) {
4793 if (uint32_t(dEndY
+ 1 - rgStartY
) >= cellMap
->mRows
.Length())
4795 const nsCellMap::CellDataArray
& row2
=
4796 cellMap
->mRows
[dEndY
+ 1 - rgStartY
];
4797 for (int32_t x
= dStartX
; x
<= dEndX
; x
++) {
4798 CellData
* cellData
= row2
.SafeElementAt(x
);
4799 if (cellData
&& (cellData
->IsRowSpan())) {
4806 // check for spanners on the left and right
4809 if ((dStartY
>= rgStartY
) && (dStartY
<= rgEndY
)) {
4810 // the damage area starts in the row group
4811 iterStartY
= dStartY
;
4812 iterEndY
= std::min(dEndY
, rgEndY
);
4813 } else if ((dEndY
>= rgStartY
) && (dEndY
<= rgEndY
)) {
4814 // the damage area ends in the row group
4815 iterStartY
= rgStartY
;
4817 } else if ((rgStartY
>= dStartY
) && (rgEndY
<= dEndY
)) {
4818 // the damage area contains the row group
4819 iterStartY
= rgStartY
;
4822 // the damage area does not overlap the row group
4825 NS_ASSERTION(iterStartY
>= 0 && iterEndY
>= 0,
4826 "table index values are expected to be nonnegative");
4827 for (int32_t y
= iterStartY
; y
<= iterEndY
; y
++) {
4828 if (uint32_t(y
- rgStartY
) >= cellMap
->mRows
.Length()) ABORT0();
4829 const nsCellMap::CellDataArray
& row
= cellMap
->mRows
[y
- rgStartY
];
4830 CellData
* cellData
= row
.SafeElementAt(dStartX
);
4831 if (cellData
&& (cellData
->IsColSpan())) {
4835 if (dEndX
< (numCols
- 1)) {
4836 cellData
= row
.SafeElementAt(dEndX
+ 1);
4837 if (cellData
&& (cellData
->IsColSpan())) {
4846 // make the damage area the whole table
4847 aArea
.StartCol() = 0;
4848 aArea
.StartRow() = 0;
4849 aArea
.ColCount() = numCols
;
4850 aArea
.RowCount() = numRows
;
4852 aArea
.StartCol() = dStartX
;
4853 aArea
.StartRow() = dStartY
;
4854 aArea
.ColCount() = 1 + dEndX
- dStartX
;
4855 aArea
.RowCount() = 1 + dEndY
- dStartY
;
4859 #define ADJACENT true
4860 #define INLINE_DIR true
4862 void BCMapCellInfo::SetTableBStartBorderWidth(BCPixelSize aWidth
) {
4863 mTableBCData
->mBStartBorderWidth
=
4864 std::max(mTableBCData
->mBStartBorderWidth
, aWidth
);
4867 void BCMapCellInfo::SetTableIStartBorderWidth(int32_t aRowB
,
4868 BCPixelSize aWidth
) {
4869 // update the iStart first cell border
4871 mTableBCData
->mIStartCellBorderWidth
= aWidth
;
4873 mTableBCData
->mIStartBorderWidth
=
4874 std::max(mTableBCData
->mIStartBorderWidth
, aWidth
);
4877 void BCMapCellInfo::SetTableIEndBorderWidth(int32_t aRowB
, BCPixelSize aWidth
) {
4878 // update the iEnd first cell border
4880 mTableBCData
->mIEndCellBorderWidth
= aWidth
;
4882 mTableBCData
->mIEndBorderWidth
=
4883 std::max(mTableBCData
->mIEndBorderWidth
, aWidth
);
4886 void BCMapCellInfo::SetIEndBorderWidths(BCPixelSize aWidth
) {
4887 // update the borders of the cells and cols affected
4889 mCell
->SetBorderWidth(
4891 std::max(aWidth
, mCell
->GetBorderWidth(LogicalSide::IEnd
)));
4894 BCPixelSize half
= BC_BORDER_START_HALF(aWidth
);
4895 mEndCol
->SetIEndBorderWidth(std::max(half
, mEndCol
->GetIEndBorderWidth()));
4899 void BCMapCellInfo::SetBEndBorderWidths(BCPixelSize aWidth
) {
4900 // update the borders of the affected cells and rows
4902 mCell
->SetBorderWidth(
4904 std::max(aWidth
, mCell
->GetBorderWidth(LogicalSide::BEnd
)));
4907 BCPixelSize half
= BC_BORDER_START_HALF(aWidth
);
4908 mEndRow
->SetBEndBCBorderWidth(
4909 std::max(half
, mEndRow
->GetBEndBCBorderWidth()));
4913 void BCMapCellInfo::SetBStartBorderWidths(BCPixelSize aWidth
) {
4915 mCell
->SetBorderWidth(
4916 LogicalSide::BStart
,
4917 std::max(aWidth
, mCell
->GetBorderWidth(LogicalSide::BStart
)));
4920 BCPixelSize half
= BC_BORDER_END_HALF(aWidth
);
4921 mStartRow
->SetBStartBCBorderWidth(
4922 std::max(half
, mStartRow
->GetBStartBCBorderWidth()));
4926 void BCMapCellInfo::SetIStartBorderWidths(BCPixelSize aWidth
) {
4928 mCell
->SetBorderWidth(
4929 LogicalSide::IStart
,
4930 std::max(aWidth
, mCell
->GetBorderWidth(LogicalSide::IStart
)));
4933 BCPixelSize half
= BC_BORDER_END_HALF(aWidth
);
4934 mStartCol
->SetIStartBorderWidth(
4935 std::max(half
, mStartCol
->GetIStartBorderWidth()));
4939 void BCMapCellInfo::SetTableBEndBorderWidth(BCPixelSize aWidth
) {
4940 mTableBCData
->mBEndBorderWidth
=
4941 std::max(mTableBCData
->mBEndBorderWidth
, aWidth
);
4944 void BCMapCellInfo::SetColumn(int32_t aColX
) {
4945 mCurrentColFrame
= mTableFirstInFlow
->GetColFrame(aColX
);
4946 mCurrentColGroupFrame
=
4947 static_cast<nsTableColGroupFrame
*>(mCurrentColFrame
->GetParent());
4948 if (!mCurrentColGroupFrame
) {
4949 NS_ERROR("null mCurrentColGroupFrame");
4953 void BCMapCellInfo::IncrementRow(bool aResetToBStartRowOfCell
) {
4955 aResetToBStartRowOfCell
? mStartRow
: mCurrentRowFrame
->GetNextRow();
4958 BCCellBorder
BCMapCellInfo::GetBStartEdgeBorder() {
4959 return CompareBorders(mTableFrame
, mCurrentColGroupFrame
, mCurrentColFrame
,
4960 mRowGroup
, mStartRow
, mCell
, mTableWM
,
4961 LogicalSide::BStart
, !ADJACENT
);
4964 BCCellBorder
BCMapCellInfo::GetBEndEdgeBorder() {
4965 return CompareBorders(mTableFrame
, mCurrentColGroupFrame
, mCurrentColFrame
,
4966 mRowGroup
, mEndRow
, mCell
, mTableWM
, LogicalSide::BEnd
,
4969 BCCellBorder
BCMapCellInfo::GetIStartEdgeBorder() {
4970 return CompareBorders(mTableFrame
, mColGroup
, mStartCol
, mRowGroup
,
4971 mCurrentRowFrame
, mCell
, mTableWM
, LogicalSide::IStart
,
4974 BCCellBorder
BCMapCellInfo::GetIEndEdgeBorder() {
4975 return CompareBorders(mTableFrame
, mColGroup
, mEndCol
, mRowGroup
,
4976 mCurrentRowFrame
, mCell
, mTableWM
, LogicalSide::IEnd
,
4979 BCCellBorder
BCMapCellInfo::GetIEndInternalBorder() {
4980 const nsIFrame
* cg
= mCgAtEnd
? mColGroup
: nullptr;
4981 return CompareBorders(nullptr, cg
, mEndCol
, nullptr, nullptr, mCell
, mTableWM
,
4982 LogicalSide::IEnd
, ADJACENT
);
4985 BCCellBorder
BCMapCellInfo::GetIStartInternalBorder() {
4986 const nsIFrame
* cg
= mCgAtStart
? mColGroup
: nullptr;
4987 return CompareBorders(nullptr, cg
, mStartCol
, nullptr, nullptr, mCell
,
4988 mTableWM
, LogicalSide::IStart
, !ADJACENT
);
4991 BCCellBorder
BCMapCellInfo::GetBEndInternalBorder() {
4992 const nsIFrame
* rg
= mRgAtEnd
? mRowGroup
: nullptr;
4993 return CompareBorders(nullptr, nullptr, nullptr, rg
, mEndRow
, mCell
, mTableWM
,
4994 LogicalSide::BEnd
, ADJACENT
);
4997 BCCellBorder
BCMapCellInfo::GetBStartInternalBorder() {
4998 const nsIFrame
* rg
= mRgAtStart
? mRowGroup
: nullptr;
4999 return CompareBorders(nullptr, nullptr, nullptr, rg
, mStartRow
, mCell
,
5000 mTableWM
, LogicalSide::BStart
, !ADJACENT
);
5003 // Calculate border information for border-collapsed tables.
5004 // Because borders of table/row/cell, etc merge into one, we need to
5005 // determine which border dominates at each cell. In addition, corner-specific
5006 // information, e.g. bevelling, is computed as well.
5008 // Here is the order for storing border edges in the cell map as a cell is
5011 // For each cell, at least 4 edges are processed:
5012 // * There are colspan * N block-start and block-end edges.
5013 // * There are rowspan * N inline-start and inline-end edges.
5015 // 1) If the cell being processed is at the block-start of the table, store the
5016 // block-start edge.
5017 // 2) If the cell being processed is at the inline-start of the table, store
5019 // inline-start edge.
5020 // 3) Store the inline-end edge.
5021 // 4) Store the block-end edge.
5023 // These steps are traced by calls to `SetBCBorderEdge`.
5025 // Corners are indexed by columns only, to avoid allocating a full row * col
5026 // array of `BCCornerInfo`. This trades off memory allocation versus moving
5027 // previous corner information around.
5030 // 1) If the cell is at the block-start of the table, but not at the
5031 // inline-start of the table, store its block-start inline-start corner.
5033 // 2) If the cell is at the inline-start of the table, store the block-start
5034 // inline-start corner.
5036 // 3) If the cell is at the block-start inline-end of the table, or not at the
5037 // block-start of the table, store the block-start inline-end corner.
5039 // 4) If the cell is at the block-end inline-end of the table, store the
5040 // block-end inline-end corner.
5042 // 5) If the cell is at the block-end of the table, store the block-end
5045 // Visually, it looks like this:
5047 // 2--1--1--1--1--1--3
5049 // 2--3--3--3--3--3--3
5051 // 2--3--3--3--3--3--3
5053 // 5--5--5--5--5--5--4
5055 // For rowspan/colspan cells, the latest border information is propagated
5056 // along its "corners".
5058 // These steps are traced by calls to `SetBCBorderCorner`.
5059 void nsTableFrame::CalcBCBorders() {
5060 NS_ASSERTION(IsBorderCollapse(),
5061 "calling CalcBCBorders on separated-border table");
5062 nsTableCellMap
* tableCellMap
= GetCellMap();
5063 if (!tableCellMap
) ABORT0();
5064 int32_t numRows
= GetRowCount();
5065 int32_t numCols
= GetColCount();
5066 if (!numRows
|| !numCols
) return; // nothing to do
5068 // Get the property holding the table damage area and border widths
5069 TableBCData
* propData
= GetTableBCData();
5070 if (!propData
) ABORT0();
5072 TableArea
damageArea(propData
->mDamageArea
);
5073 // See documentation for why we do this.
5074 ExpandBCDamageArea(damageArea
);
5076 // We accumulate border widths as we process the cells, so we need
5077 // to reset it once in the beginning.
5078 bool tableBorderReset
[4];
5079 for (uint32_t sideX
= 0; sideX
< ArrayLength(tableBorderReset
); sideX
++) {
5080 tableBorderReset
[sideX
] = false;
5083 // Storage for block-direction borders from the previous row, indexed by
5085 BCCellBorders
lastBlockDirBorders(damageArea
.ColCount() + 1,
5086 damageArea
.StartCol());
5087 if (!lastBlockDirBorders
.borders
) ABORT0();
5088 if (damageArea
.StartRow() != 0) {
5089 // Ok, we've filled with information about the previous row's borders with
5090 // the default state, which is "no borders." This is incorrect, and leaving
5091 // it will result in an erroneous behaviour if the previous row did have
5092 // borders, and the dirty rows don't, as we will not mark the beginning of
5093 // the no border segment.
5094 TableArea
prevRowArea(damageArea
.StartCol(), damageArea
.StartRow() - 1,
5095 damageArea
.ColCount(), 1);
5096 BCMapCellIterator
iter(this, prevRowArea
);
5097 BCMapCellInfo
info(this);
5098 for (iter
.First(info
); !iter
.mAtEnd
; iter
.Next(info
)) {
5099 if (info
.mColIndex
== prevRowArea
.StartCol()) {
5100 lastBlockDirBorders
.borders
[0] = info
.GetIStartEdgeBorder();
5102 lastBlockDirBorders
.borders
[info
.mColIndex
- prevRowArea
.StartCol() + 1] =
5103 info
.GetIEndEdgeBorder();
5106 // Inline direction border at block start of the table, computed by the
5107 // previous cell. Unused afterwards.
5108 Maybe
<BCCellBorder
> firstRowBStartEdgeBorder
;
5109 BCCellBorder lastBEndBorder
;
5110 // Storage for inline-direction borders from previous cells, indexed by
5112 // TODO(dshin): Why ColCount + 1? Number of inline segments should match
5113 // column count exactly, unlike block direction segments...
5114 BCCellBorders
lastBEndBorders(damageArea
.ColCount() + 1,
5115 damageArea
.StartCol());
5116 if (!lastBEndBorders
.borders
) ABORT0();
5118 BCMapCellInfo
info(this);
5120 // Block-start corners of the cell being traversed, indexed by columns.
5121 BCCorners
bStartCorners(damageArea
.ColCount() + 1, damageArea
.StartCol());
5122 if (!bStartCorners
.corners
) ABORT0();
5123 // Block-end corners of the cell being traversed, indexed by columns.
5124 // Note that when a new row starts, they become block-start corners and used
5125 // as such, until cleared with `Set`.
5126 BCCorners
bEndCorners(damageArea
.ColCount() + 1, damageArea
.StartCol());
5127 if (!bEndCorners
.corners
) ABORT0();
5129 BCMapCellIterator
iter(this, damageArea
);
5130 for (iter
.First(info
); !iter
.mAtEnd
; iter
.Next(info
)) {
5131 // see if firstRowBStartEdgeBorder, lastBEndBorder need to be reset
5132 if (iter
.IsNewRow()) {
5133 if (info
.mRowIndex
== 0) {
5134 BCCellBorder border
;
5135 if (info
.mColIndex
== 0) {
5136 border
.Reset(info
.mRowIndex
, info
.mRowSpan
);
5138 // Similar to lastBlockDirBorders, the previous block-start border
5139 // is filled by actually quering the adjacent cell.
5140 BCMapCellInfo
ajaInfo(this);
5141 iter
.PeekIStart(info
, info
.mRowIndex
, ajaInfo
);
5142 border
= ajaInfo
.GetBStartEdgeBorder();
5144 firstRowBStartEdgeBorder
= Some(border
);
5146 firstRowBStartEdgeBorder
= Nothing
{};
5148 if (info
.mColIndex
== 0) {
5149 lastBEndBorder
.Reset(info
.GetCellEndRowIndex() + 1, info
.mRowSpan
);
5151 // Same as above, but for block-end border.
5152 BCMapCellInfo
ajaInfo(this);
5153 iter
.PeekIStart(info
, info
.mRowIndex
, ajaInfo
);
5154 lastBEndBorder
= ajaInfo
.GetBEndEdgeBorder();
5156 } else if (info
.mColIndex
> damageArea
.StartCol()) {
5157 lastBEndBorder
= lastBEndBorders
[info
.mColIndex
- 1];
5158 if (lastBEndBorder
.rowIndex
> (info
.GetCellEndRowIndex() + 1)) {
5159 // the bEnd border's iStart edge butts against the middle of a rowspan
5160 lastBEndBorder
.Reset(info
.GetCellEndRowIndex() + 1, info
.mRowSpan
);
5164 // find the dominant border considering the cell's bStart border and the
5165 // table, row group, row if the border is at the bStart of the table,
5166 // otherwise it was processed in a previous row
5167 if (0 == info
.mRowIndex
) {
5168 uint8_t idxBStart
= static_cast<uint8_t>(LogicalSide::BStart
);
5169 if (!tableBorderReset
[idxBStart
]) {
5170 propData
->mBStartBorderWidth
= 0;
5171 tableBorderReset
[idxBStart
] = true;
5173 for (int32_t colIdx
= info
.mColIndex
; colIdx
<= info
.GetCellEndColIndex();
5175 info
.SetColumn(colIdx
);
5176 BCCellBorder currentBorder
= info
.GetBStartEdgeBorder();
5177 BCCornerInfo
& bStartIStartCorner
= bStartCorners
[colIdx
];
5178 // Mark inline-end direction border from this corner.
5180 bStartIStartCorner
.Set(LogicalSide::IEnd
, currentBorder
);
5182 bStartIStartCorner
.Update(LogicalSide::IEnd
, currentBorder
);
5183 tableCellMap
->SetBCBorderCorner(
5184 LogicalCorner::BStartIStart
, *iter
.mCellMap
, 0, 0, colIdx
,
5185 LogicalSide(bStartIStartCorner
.ownerSide
),
5186 bStartIStartCorner
.subWidth
, bStartIStartCorner
.bevel
);
5188 // Above, we set the corner `colIndex` column as having a border towards
5189 // inline-end, heading towards the next column. Vice versa is also true,
5190 // where the next column has a border heading towards this column.
5191 bStartCorners
[colIdx
+ 1].Set(LogicalSide::IStart
, currentBorder
);
5192 MOZ_ASSERT(firstRowBStartEdgeBorder
,
5193 "Inline start border tracking not set?");
5194 // update firstRowBStartEdgeBorder and see if a new segment starts
5196 firstRowBStartEdgeBorder
5197 ? SetInlineDirBorder(currentBorder
, bStartIStartCorner
,
5198 firstRowBStartEdgeBorder
.ref())
5200 // store the border segment in the cell map
5201 tableCellMap
->SetBCBorderEdge(LogicalSide::BStart
, *iter
.mCellMap
, 0, 0,
5202 colIdx
, 1, currentBorder
.owner
,
5203 currentBorder
.width
, startSeg
);
5205 // Set border width at block-start (table-wide and for the cell), but
5206 // only if it's the largest we've encountered.
5207 info
.SetTableBStartBorderWidth(currentBorder
.width
);
5208 info
.SetBStartBorderWidths(currentBorder
.width
);
5211 // see if the bStart border needs to be the start of a segment due to a
5212 // block-dir border owning the corner
5213 if (info
.mColIndex
> 0) {
5214 BCData
& data
= info
.mCellData
->mData
;
5215 if (!data
.IsBStartStart()) {
5216 LogicalSide cornerSide
;
5218 data
.GetCorner(cornerSide
, bevel
);
5219 if (IsBlock(cornerSide
)) {
5220 data
.SetBStartStart(true);
5226 // find the dominant border considering the cell's iStart border and the
5227 // table, col group, col if the border is at the iStart of the table,
5228 // otherwise it was processed in a previous col
5229 if (0 == info
.mColIndex
) {
5230 uint8_t idxIStart
= static_cast<uint8_t>(LogicalSide::IStart
);
5231 if (!tableBorderReset
[idxIStart
]) {
5232 propData
->mIStartBorderWidth
= 0;
5233 tableBorderReset
[idxIStart
] = true;
5235 info
.mCurrentRowFrame
= nullptr;
5236 for (int32_t rowB
= info
.mRowIndex
; rowB
<= info
.GetCellEndRowIndex();
5238 info
.IncrementRow(rowB
== info
.mRowIndex
);
5239 BCCellBorder currentBorder
= info
.GetIStartEdgeBorder();
5240 BCCornerInfo
& bStartIStartCorner
=
5241 (0 == rowB
) ? bStartCorners
[0] : bEndCorners
[0];
5242 bStartIStartCorner
.Update(LogicalSide::BEnd
, currentBorder
);
5243 tableCellMap
->SetBCBorderCorner(
5244 LogicalCorner::BStartIStart
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5245 rowB
, 0, LogicalSide(bStartIStartCorner
.ownerSide
),
5246 bStartIStartCorner
.subWidth
, bStartIStartCorner
.bevel
);
5247 bEndCorners
[0].Set(LogicalSide::BStart
, currentBorder
);
5249 // update lastBlockDirBorders and see if a new segment starts
5250 bool startSeg
= SetBorder(currentBorder
, lastBlockDirBorders
[0]);
5251 // store the border segment in the cell map
5252 tableCellMap
->SetBCBorderEdge(LogicalSide::IStart
, *iter
.mCellMap
,
5253 iter
.mRowGroupStart
, rowB
, info
.mColIndex
,
5254 1, currentBorder
.owner
,
5255 currentBorder
.width
, startSeg
);
5256 // Set border width at inline-start (table-wide and for the cell), but
5257 // only if it's the largest we've encountered.
5258 info
.SetTableIStartBorderWidth(rowB
, currentBorder
.width
);
5259 info
.SetIStartBorderWidths(currentBorder
.width
);
5263 // find the dominant border considering the cell's iEnd border, adjacent
5264 // cells and the table, row group, row
5265 if (info
.mNumTableCols
== info
.GetCellEndColIndex() + 1) {
5266 // touches iEnd edge of table
5267 uint8_t idxIEnd
= static_cast<uint8_t>(LogicalSide::IEnd
);
5268 if (!tableBorderReset
[idxIEnd
]) {
5269 propData
->mIEndBorderWidth
= 0;
5270 tableBorderReset
[idxIEnd
] = true;
5272 info
.mCurrentRowFrame
= nullptr;
5273 for (int32_t rowB
= info
.mRowIndex
; rowB
<= info
.GetCellEndRowIndex();
5275 info
.IncrementRow(rowB
== info
.mRowIndex
);
5276 BCCellBorder currentBorder
= info
.GetIEndEdgeBorder();
5277 // Update/store the bStart-iEnd & bEnd-iEnd corners. Note that we
5278 // overwrite all corner information to the end of the column span.
5279 BCCornerInfo
& bStartIEndCorner
=
5280 (0 == rowB
) ? bStartCorners
[info
.GetCellEndColIndex() + 1]
5281 : bEndCorners
[info
.GetCellEndColIndex() + 1];
5282 bStartIEndCorner
.Update(LogicalSide::BEnd
, currentBorder
);
5283 tableCellMap
->SetBCBorderCorner(
5284 LogicalCorner::BStartIEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5285 rowB
, info
.GetCellEndColIndex(),
5286 LogicalSide(bStartIEndCorner
.ownerSide
), bStartIEndCorner
.subWidth
,
5287 bStartIEndCorner
.bevel
);
5288 BCCornerInfo
& bEndIEndCorner
=
5289 bEndCorners
[info
.GetCellEndColIndex() + 1];
5290 bEndIEndCorner
.Set(LogicalSide::BStart
, currentBorder
);
5291 tableCellMap
->SetBCBorderCorner(
5292 LogicalCorner::BEndIEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
, rowB
,
5293 info
.GetCellEndColIndex(), LogicalSide(bEndIEndCorner
.ownerSide
),
5294 bEndIEndCorner
.subWidth
, bEndIEndCorner
.bevel
);
5295 // update lastBlockDirBorders and see if a new segment starts
5296 bool startSeg
= SetBorder(
5297 currentBorder
, lastBlockDirBorders
[info
.GetCellEndColIndex() + 1]);
5298 // store the border segment in the cell map and update cellBorders
5299 tableCellMap
->SetBCBorderEdge(
5300 LogicalSide::IEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
, rowB
,
5301 info
.GetCellEndColIndex(), 1, currentBorder
.owner
,
5302 currentBorder
.width
, startSeg
);
5303 // Set border width at inline-end (table-wide and for the cell), but
5304 // only if it's the largest we've encountered.
5305 info
.SetTableIEndBorderWidth(rowB
, currentBorder
.width
);
5306 info
.SetIEndBorderWidths(currentBorder
.width
);
5309 // Cell entries, but not on the block-end side of the entire table.
5310 int32_t segLength
= 0;
5311 BCMapCellInfo
ajaInfo(this);
5312 BCMapCellInfo
priorAjaInfo(this);
5313 for (int32_t rowB
= info
.mRowIndex
; rowB
<= info
.GetCellEndRowIndex();
5314 rowB
+= segLength
) {
5315 // Grab the cell adjacent to our inline-end.
5316 iter
.PeekIEnd(info
, rowB
, ajaInfo
);
5317 BCCellBorder currentBorder
= info
.GetIEndInternalBorder();
5318 BCCellBorder adjacentBorder
= ajaInfo
.GetIStartInternalBorder();
5319 currentBorder
= CompareBorders(!CELL_CORNER
, currentBorder
,
5320 adjacentBorder
, !INLINE_DIR
);
5322 segLength
= std::max(1, ajaInfo
.mRowIndex
+ ajaInfo
.mRowSpan
- rowB
);
5323 segLength
= std::min(segLength
, info
.mRowIndex
+ info
.mRowSpan
- rowB
);
5325 // update lastBlockDirBorders and see if a new segment starts
5326 bool startSeg
= SetBorder(
5327 currentBorder
, lastBlockDirBorders
[info
.GetCellEndColIndex() + 1]);
5328 // store the border segment in the cell map and update cellBorders
5329 if (info
.GetCellEndColIndex() < damageArea
.EndCol() &&
5330 rowB
>= damageArea
.StartRow() && rowB
< damageArea
.EndRow()) {
5331 tableCellMap
->SetBCBorderEdge(
5332 LogicalSide::IEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
, rowB
,
5333 info
.GetCellEndColIndex(), segLength
, currentBorder
.owner
,
5334 currentBorder
.width
, startSeg
);
5335 info
.SetIEndBorderWidths(currentBorder
.width
);
5336 ajaInfo
.SetIStartBorderWidths(currentBorder
.width
);
5338 // Does the block-start inline-end corner hit the inline-end adjacent
5339 // cell that wouldn't have an inline border? e.g.
5341 // o-----------o---------------o
5343 // o-----------x Adjacent cell o
5344 // | This Cell | (rowspan) |
5345 // o-----------o---------------o
5346 bool hitsSpanOnIEnd
= (rowB
> ajaInfo
.mRowIndex
) &&
5347 (rowB
< ajaInfo
.mRowIndex
+ ajaInfo
.mRowSpan
);
5348 BCCornerInfo
* bStartIEndCorner
=
5349 ((0 == rowB
) || hitsSpanOnIEnd
)
5350 ? &bStartCorners
[info
.GetCellEndColIndex() + 1]
5351 : &bEndCorners
[info
.GetCellEndColIndex() +
5352 1]; // From previous row.
5353 bStartIEndCorner
->Update(LogicalSide::BEnd
, currentBorder
);
5354 // If this is a rowspan, need to consider if this "corner" is generating
5355 // an inline segment for the adjacent cell. e.g.
5357 // o--------------o----o
5360 // | (This "row") | |
5361 // o--------------o----o
5362 if (rowB
!= info
.mRowIndex
) {
5363 currentBorder
= priorAjaInfo
.GetBEndInternalBorder();
5364 BCCellBorder adjacentBorder
= ajaInfo
.GetBStartInternalBorder();
5365 currentBorder
= CompareBorders(!CELL_CORNER
, currentBorder
,
5366 adjacentBorder
, INLINE_DIR
);
5367 bStartIEndCorner
->Update(LogicalSide::IEnd
, currentBorder
);
5369 // Check that the spanned area is inside of the invalidation area
5370 if (info
.GetCellEndColIndex() < damageArea
.EndCol() &&
5371 rowB
>= damageArea
.StartRow()) {
5373 // Ok, actually store the information
5374 tableCellMap
->SetBCBorderCorner(
5375 LogicalCorner::BStartIEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5376 rowB
, info
.GetCellEndColIndex(),
5377 LogicalSide(bStartIEndCorner
->ownerSide
),
5378 bStartIEndCorner
->subWidth
, bStartIEndCorner
->bevel
);
5380 // Propagate this segment down the rowspan
5381 for (int32_t rX
= rowB
+ 1; rX
< rowB
+ segLength
; rX
++) {
5382 tableCellMap
->SetBCBorderCorner(
5383 LogicalCorner::BEndIEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5384 rX
, info
.GetCellEndColIndex(),
5385 LogicalSide(bStartIEndCorner
->ownerSide
),
5386 bStartIEndCorner
->subWidth
, false);
5390 (rowB
+ segLength
< ajaInfo
.mRowIndex
+ ajaInfo
.mRowSpan
);
5391 BCCornerInfo
& bEndIEndCorner
=
5392 (hitsSpanOnIEnd
) ? bStartCorners
[info
.GetCellEndColIndex() + 1]
5393 : bEndCorners
[info
.GetCellEndColIndex() + 1];
5394 bEndIEndCorner
.Set(LogicalSide::BStart
, currentBorder
);
5395 priorAjaInfo
= ajaInfo
;
5398 for (int32_t colIdx
= info
.mColIndex
+ 1;
5399 colIdx
<= info
.GetCellEndColIndex(); colIdx
++) {
5400 lastBlockDirBorders
[colIdx
].Reset(0, 1);
5403 // find the dominant border considering the cell's bEnd border, adjacent
5404 // cells and the table, row group, row
5405 if (info
.mNumTableRows
== info
.GetCellEndRowIndex() + 1) {
5406 // touches bEnd edge of table
5407 uint8_t idxBEnd
= static_cast<uint8_t>(LogicalSide::BEnd
);
5408 if (!tableBorderReset
[idxBEnd
]) {
5409 propData
->mBEndBorderWidth
= 0;
5410 tableBorderReset
[idxBEnd
] = true;
5412 for (int32_t colIdx
= info
.mColIndex
; colIdx
<= info
.GetCellEndColIndex();
5414 info
.SetColumn(colIdx
);
5415 BCCellBorder currentBorder
= info
.GetBEndEdgeBorder();
5416 BCCornerInfo
& bEndIStartCorner
= bEndCorners
[colIdx
];
5417 bEndIStartCorner
.Update(LogicalSide::IEnd
, currentBorder
);
5418 tableCellMap
->SetBCBorderCorner(
5419 LogicalCorner::BEndIStart
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5420 info
.GetCellEndRowIndex(), colIdx
,
5421 LogicalSide(bEndIStartCorner
.ownerSide
), bEndIStartCorner
.subWidth
,
5422 bEndIStartCorner
.bevel
);
5423 BCCornerInfo
& bEndIEndCorner
= bEndCorners
[colIdx
+ 1];
5424 bEndIEndCorner
.Update(LogicalSide::IStart
, currentBorder
);
5425 // Store the block-end inline-end corner if it also is the block-end
5426 // inline-end of the overall table.
5427 if (info
.mNumTableCols
== colIdx
+ 1) {
5428 tableCellMap
->SetBCBorderCorner(
5429 LogicalCorner::BEndIEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5430 info
.GetCellEndRowIndex(), colIdx
,
5431 LogicalSide(bEndIEndCorner
.ownerSide
), bEndIEndCorner
.subWidth
,
5432 bEndIEndCorner
.bevel
, true);
5434 // update lastBEndBorder and see if a new segment starts
5436 SetInlineDirBorder(currentBorder
, bEndIStartCorner
, lastBEndBorder
);
5438 // make sure that we did not compare apples to oranges i.e. the
5439 // current border should be a continuation of the lastBEndBorder,
5440 // as it is a bEnd border
5441 // add 1 to the info.GetCellEndRowIndex()
5443 (lastBEndBorder
.rowIndex
!= (info
.GetCellEndRowIndex() + 1));
5445 // store the border segment in the cell map and update cellBorders
5446 tableCellMap
->SetBCBorderEdge(
5447 LogicalSide::BEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5448 info
.GetCellEndRowIndex(), colIdx
, 1, currentBorder
.owner
,
5449 currentBorder
.width
, startSeg
);
5450 // update lastBEndBorders
5451 lastBEndBorder
.rowIndex
= info
.GetCellEndRowIndex() + 1;
5452 lastBEndBorder
.rowSpan
= info
.mRowSpan
;
5453 lastBEndBorders
[colIdx
] = lastBEndBorder
;
5455 // Set border width at block-end (table-wide and for the cell), but
5456 // only if it's the largest we've encountered.
5457 info
.SetBEndBorderWidths(currentBorder
.width
);
5458 info
.SetTableBEndBorderWidth(currentBorder
.width
);
5461 int32_t segLength
= 0;
5462 BCMapCellInfo
ajaInfo(this);
5463 for (int32_t colIdx
= info
.mColIndex
; colIdx
<= info
.GetCellEndColIndex();
5464 colIdx
+= segLength
) {
5465 // Grab the cell adjacent to our block-end.
5466 iter
.PeekBEnd(info
, colIdx
, ajaInfo
);
5467 BCCellBorder currentBorder
= info
.GetBEndInternalBorder();
5468 BCCellBorder adjacentBorder
= ajaInfo
.GetBStartInternalBorder();
5469 currentBorder
= CompareBorders(!CELL_CORNER
, currentBorder
,
5470 adjacentBorder
, INLINE_DIR
);
5471 segLength
= std::max(1, ajaInfo
.mColIndex
+ ajaInfo
.mColSpan
- colIdx
);
5473 std::min(segLength
, info
.mColIndex
+ info
.mColSpan
- colIdx
);
5475 BCCornerInfo
& bEndIStartCorner
= bEndCorners
[colIdx
];
5482 bool hitsSpanBelow
= (colIdx
> ajaInfo
.mColIndex
) &&
5483 (colIdx
< ajaInfo
.mColIndex
+ ajaInfo
.mColSpan
);
5485 if (colIdx
== info
.mColIndex
&& colIdx
> damageArea
.StartCol()) {
5486 int32_t prevRowIndex
= lastBEndBorders
[colIdx
- 1].rowIndex
;
5487 if (prevRowIndex
> info
.GetCellEndRowIndex() + 1) {
5488 // hits a rowspan on the iEnd side
5490 // the corner was taken care of during the cell on the iStart side
5491 } else if (prevRowIndex
< info
.GetCellEndRowIndex() + 1) {
5492 // spans below the cell to the iStart side
5493 bStartCorners
[colIdx
] = bEndIStartCorner
;
5494 bEndIStartCorner
.Set(LogicalSide::IEnd
, currentBorder
);
5499 bEndIStartCorner
.Update(LogicalSide::IEnd
, currentBorder
);
5501 // Check that the spanned area is inside of the invalidation area
5502 if (info
.GetCellEndRowIndex() < damageArea
.EndRow() &&
5503 colIdx
>= damageArea
.StartCol()) {
5504 if (hitsSpanBelow
) {
5505 tableCellMap
->SetBCBorderCorner(
5506 LogicalCorner::BEndIStart
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5507 info
.GetCellEndRowIndex(), colIdx
,
5508 LogicalSide(bEndIStartCorner
.ownerSide
),
5509 bEndIStartCorner
.subWidth
, bEndIStartCorner
.bevel
);
5511 // Propagate this segment down the colspan
5512 for (int32_t c
= colIdx
+ 1; c
< colIdx
+ segLength
; c
++) {
5513 BCCornerInfo
& corner
= bEndCorners
[c
];
5514 corner
.Set(LogicalSide::IEnd
, currentBorder
);
5515 tableCellMap
->SetBCBorderCorner(
5516 LogicalCorner::BEndIStart
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5517 info
.GetCellEndRowIndex(), c
, LogicalSide(corner
.ownerSide
),
5518 corner
.subWidth
, false);
5521 // update lastBEndBorders and see if a new segment starts
5523 SetInlineDirBorder(currentBorder
, bEndIStartCorner
, lastBEndBorder
);
5525 // make sure that we did not compare apples to oranges i.e. the
5526 // current border should be a continuation of the lastBEndBorder,
5527 // as it is a bEnd border
5528 // add 1 to the info.GetCellEndRowIndex()
5529 startSeg
= (lastBEndBorder
.rowIndex
!= info
.GetCellEndRowIndex() + 1);
5531 lastBEndBorder
.rowIndex
= info
.GetCellEndRowIndex() + 1;
5532 lastBEndBorder
.rowSpan
= info
.mRowSpan
;
5533 for (int32_t c
= colIdx
; c
< colIdx
+ segLength
; c
++) {
5534 lastBEndBorders
[c
] = lastBEndBorder
;
5537 // store the border segment the cell map and update cellBorders
5538 if (info
.GetCellEndRowIndex() < damageArea
.EndRow() &&
5539 colIdx
>= damageArea
.StartCol() && colIdx
< damageArea
.EndCol()) {
5540 tableCellMap
->SetBCBorderEdge(
5541 LogicalSide::BEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5542 info
.GetCellEndRowIndex(), colIdx
, segLength
, currentBorder
.owner
,
5543 currentBorder
.width
, startSeg
);
5544 info
.SetBEndBorderWidths(currentBorder
.width
);
5545 ajaInfo
.SetBStartBorderWidths(currentBorder
.width
);
5547 // update bEnd-iEnd corner
5548 BCCornerInfo
& bEndIEndCorner
= bEndCorners
[colIdx
+ segLength
];
5549 bEndIEndCorner
.Update(LogicalSide::IStart
, currentBorder
);
5557 // We normally join edges of successive block-end inline segments by
5558 // consulting the previous segment; however, cell c2's block-end inline
5559 // segment e2 is processed before e1, so we need to process such joins
5560 // out-of-band here, when we're processing c3.
5561 const auto nextColIndex
= info
.GetCellEndColIndex() + 1;
5562 if ((info
.mNumTableCols
!= nextColIndex
) &&
5563 (lastBEndBorders
[nextColIndex
].rowSpan
> 1) &&
5564 (lastBEndBorders
[nextColIndex
].rowIndex
==
5565 info
.GetCellEndRowIndex() + 1)) {
5566 BCCornerInfo
& corner
= bEndCorners
[nextColIndex
];
5567 if (!IsBlock(LogicalSide(corner
.ownerSide
))) {
5568 // not a block-dir owner
5569 BCCellBorder
& thisBorder
= lastBEndBorder
;
5570 BCCellBorder
& nextBorder
= lastBEndBorders
[info
.mColIndex
+ 1];
5571 if ((thisBorder
.color
== nextBorder
.color
) &&
5572 (thisBorder
.width
== nextBorder
.width
) &&
5573 (thisBorder
.style
== nextBorder
.style
)) {
5574 // set the flag on the next border indicating it is not the start of a
5576 if (iter
.mCellMap
) {
5577 tableCellMap
->ResetBStartStart(
5578 LogicalSide::BEnd
, *iter
.mCellMap
, iter
.mRowGroupStart
,
5579 info
.GetCellEndRowIndex(), nextColIndex
);
5584 } // for (iter.First(info); info.mCell; iter.Next(info)) {
5585 // reset the bc flag and damage area
5586 SetNeedToCalcBCBorders(false);
5587 propData
->mDamageArea
= TableArea(0, 0, 0, 0);
5588 #ifdef DEBUG_TABLE_CELLMAP
5593 class BCPaintBorderIterator
;
5595 struct BCBorderParameters
{
5596 StyleBorderStyle mBorderStyle
;
5597 nscolor mBorderColor
;
5599 int32_t mAppUnitsPerDevPixel
;
5600 mozilla::Side mStartBevelSide
;
5601 nscoord mStartBevelOffset
;
5602 mozilla::Side mEndBevelSide
;
5603 nscoord mEndBevelOffset
;
5604 bool mBackfaceIsVisible
;
5606 bool NeedToBevel() const {
5607 if (!mStartBevelOffset
&& !mEndBevelOffset
) {
5611 if (mBorderStyle
== StyleBorderStyle::Dashed
||
5612 mBorderStyle
== StyleBorderStyle::Dotted
) {
5620 struct BCBlockDirSeg
{
5623 void Start(BCPaintBorderIterator
& aIter
, BCBorderOwner aBorderOwner
,
5624 BCPixelSize aBlockSegISize
, BCPixelSize aInlineSegBSize
,
5625 Maybe
<nscoord
> aEmptyRowEndSize
);
5627 void Initialize(BCPaintBorderIterator
& aIter
);
5628 void GetBEndCorner(BCPaintBorderIterator
& aIter
, BCPixelSize aInlineSegBSize
);
5630 Maybe
<BCBorderParameters
> BuildBorderParameters(BCPaintBorderIterator
& aIter
,
5631 BCPixelSize aInlineSegBSize
);
5632 void Paint(BCPaintBorderIterator
& aIter
, DrawTarget
& aDrawTarget
,
5633 BCPixelSize aInlineSegBSize
);
5634 void CreateWebRenderCommands(BCPaintBorderIterator
& aIter
,
5635 BCPixelSize aInlineSegBSize
,
5636 wr::DisplayListBuilder
& aBuilder
,
5637 const layers::StackingContextHelper
& aSc
,
5638 const nsPoint
& aPt
);
5639 void AdvanceOffsetB();
5640 void IncludeCurrentBorder(BCPaintBorderIterator
& aIter
);
5643 nsTableColFrame
* mCol
;
5646 nscoord mOffsetI
; // i-offset with respect to the table edge
5647 nscoord mOffsetB
; // b-offset with respect to the table edge
5648 nscoord mLength
; // block-dir length including corners
5649 BCPixelSize mWidth
; // thickness in pixels
5651 nsTableCellFrame
* mAjaCell
; // previous sibling to the first cell
5652 // where the segment starts, it can be
5653 // the owner of a segment
5654 nsTableCellFrame
* mFirstCell
; // cell at the start of the segment
5655 nsTableRowGroupFrame
*
5656 mFirstRowGroup
; // row group at the start of the segment
5657 nsTableRowFrame
* mFirstRow
; // row at the start of the segment
5658 nsTableCellFrame
* mLastCell
; // cell at the current end of the
5661 uint8_t mOwner
; // owner of the border, defines the
5663 LogicalSide mBStartBevelSide
; // direction to bevel at the bStart
5664 nscoord mBStartBevelOffset
; // how much to bevel at the bStart
5665 BCPixelSize mBEndInlineSegBSize
; // bSize of the crossing
5666 // inline-dir border
5667 nscoord mBEndOffset
; // how much longer is the segment due
5668 // to the inline-dir border, by this
5669 // amount the next segment needs to be
5671 bool mIsBEndBevel
; // should we bevel at the bEnd
5674 struct BCInlineDirSeg
{
5677 void Start(BCPaintBorderIterator
& aIter
, BCBorderOwner aBorderOwner
,
5678 BCPixelSize aBEndBlockSegISize
, BCPixelSize aInlineSegBSize
);
5679 void GetIEndCorner(BCPaintBorderIterator
& aIter
, BCPixelSize aIStartSegISize
);
5680 void AdvanceOffsetI();
5681 void IncludeCurrentBorder(BCPaintBorderIterator
& aIter
);
5682 Maybe
<BCBorderParameters
> BuildBorderParameters(BCPaintBorderIterator
& aIter
);
5683 void Paint(BCPaintBorderIterator
& aIter
, DrawTarget
& aDrawTarget
);
5684 void CreateWebRenderCommands(BCPaintBorderIterator
& aIter
,
5685 wr::DisplayListBuilder
& aBuilder
,
5686 const layers::StackingContextHelper
& aSc
,
5687 const nsPoint
& aPt
);
5689 nscoord mOffsetI
; // i-offset with respect to the table edge
5690 nscoord mOffsetB
; // b-offset with respect to the table edge
5691 nscoord mLength
; // inline-dir length including corners
5692 BCPixelSize mWidth
; // border thickness in pixels
5693 nscoord mIStartBevelOffset
; // how much to bevel at the iStart
5694 LogicalSide mIStartBevelSide
; // direction to bevel at the iStart
5695 bool mIsIEndBevel
; // should we bevel at the iEnd end
5696 nscoord mIEndBevelOffset
; // how much to bevel at the iEnd
5697 LogicalSide mIEndBevelSide
; // direction to bevel at the iEnd
5698 nscoord mEndOffset
; // how much longer is the segment due
5699 // to the block-dir border, by this
5700 // amount the next segment needs to be
5702 uint8_t mOwner
; // owner of the border, defines the
5704 nsTableCellFrame
* mFirstCell
; // cell at the start of the segment
5705 nsTableCellFrame
* mAjaCell
; // neighboring cell to the first cell
5706 // where the segment starts, it can be
5707 // the owner of a segment
5710 struct BCPaintData
{
5711 explicit BCPaintData(DrawTarget
& aDrawTarget
) : mDrawTarget(aDrawTarget
) {}
5713 DrawTarget
& mDrawTarget
;
5716 struct BCCreateWebRenderCommandsData
{
5717 BCCreateWebRenderCommandsData(wr::DisplayListBuilder
& aBuilder
,
5718 const layers::StackingContextHelper
& aSc
,
5719 const nsPoint
& aOffsetToReferenceFrame
)
5720 : mBuilder(aBuilder
),
5722 mOffsetToReferenceFrame(aOffsetToReferenceFrame
) {}
5724 wr::DisplayListBuilder
& mBuilder
;
5725 const layers::StackingContextHelper
& mSc
;
5726 const nsPoint
& mOffsetToReferenceFrame
;
5729 struct BCPaintBorderAction
{
5730 explicit BCPaintBorderAction(DrawTarget
& aDrawTarget
)
5731 : mMode(Mode::Paint
), mPaintData(aDrawTarget
) {}
5733 BCPaintBorderAction(wr::DisplayListBuilder
& aBuilder
,
5734 const layers::StackingContextHelper
& aSc
,
5735 const nsPoint
& aOffsetToReferenceFrame
)
5736 : mMode(Mode::CreateWebRenderCommands
),
5737 mCreateWebRenderCommandsData(aBuilder
, aSc
, aOffsetToReferenceFrame
) {}
5739 ~BCPaintBorderAction() {
5740 // mCreateWebRenderCommandsData is in a union which means the destructor
5741 // wouldn't be called when BCPaintBorderAction get destroyed. So call the
5742 // destructor here explicitly.
5743 if (mMode
== Mode::CreateWebRenderCommands
) {
5744 mCreateWebRenderCommandsData
.~BCCreateWebRenderCommandsData();
5750 CreateWebRenderCommands
,
5756 BCPaintData mPaintData
;
5757 BCCreateWebRenderCommandsData mCreateWebRenderCommandsData
;
5761 // Iterates over borders (iStart border, corner, bStart border) in the cell map
5762 // within a damage area from iStart to iEnd, bStart to bEnd. All members are in
5763 // terms of the 1st in flow frames, except where suffixed by InFlow.
5764 class BCPaintBorderIterator
{
5766 explicit BCPaintBorderIterator(nsTableFrame
* aTable
);
5770 * Determine the damage area in terms of rows and columns and finalize
5771 * mInitialOffsetI and mInitialOffsetB.
5772 * @param aDirtyRect - dirty rect in table coordinates
5773 * @return - true if we need to paint something given dirty rect
5775 bool SetDamageArea(const nsRect
& aDamageRect
);
5778 void AccumulateOrDoActionInlineDirSegment(BCPaintBorderAction
& aAction
);
5779 void AccumulateOrDoActionBlockDirSegment(BCPaintBorderAction
& aAction
);
5780 void ResetVerInfo();
5781 void StoreColumnWidth(int32_t aIndex
);
5782 bool BlockDirSegmentOwnsCorner();
5784 nsTableFrame
* mTable
;
5785 nsTableFrame
* mTableFirstInFlow
;
5786 nsTableCellMap
* mTableCellMap
;
5787 nsCellMap
* mCellMap
;
5788 WritingMode mTableWM
;
5789 nsTableFrame::RowGroupArray mRowGroups
;
5791 nsTableRowGroupFrame
* mPrevRg
;
5792 nsTableRowGroupFrame
* mRg
;
5793 bool mIsRepeatedHeader
;
5794 bool mIsRepeatedFooter
;
5795 nsTableRowGroupFrame
* mStartRg
; // first row group in the damagearea
5796 int32_t mRgIndex
; // current row group index in the
5798 int32_t mFifRgFirstRowIndex
; // start row index of the first in
5799 // flow of the row group
5800 int32_t mRgFirstRowIndex
; // row index of the first row in the
5802 int32_t mRgLastRowIndex
; // row index of the last row in the row
5804 int32_t mNumTableRows
; // number of rows in the table and all
5806 int32_t mNumTableCols
; // number of columns in the table
5807 int32_t mColIndex
; // with respect to the table
5808 int32_t mRowIndex
; // with respect to the table
5809 int32_t mRepeatedHeaderRowIndex
; // row index in a repeated
5810 // header, it's equivalent to
5811 // mRowIndex when we're in a repeated
5812 // header, and set to the last row
5813 // index of a repeated header when
5816 bool mAtEnd
; // the iterator cycled over all
5818 nsTableRowFrame
* mPrevRow
;
5819 nsTableRowFrame
* mRow
;
5820 nsTableRowFrame
* mStartRow
; // first row in a inside the damagearea
5823 nsTableCellFrame
* mPrevCell
;
5824 nsTableCellFrame
* mCell
;
5825 BCCellData
* mPrevCellData
;
5826 BCCellData
* mCellData
;
5829 bool IsTableBStartMost() {
5830 return (mRowIndex
== 0) && !mTable
->GetPrevInFlow();
5832 bool IsTableIEndMost() { return (mColIndex
>= mNumTableCols
); }
5833 bool IsTableBEndMost() {
5834 return (mRowIndex
>= mNumTableRows
) && !mTable
->GetNextInFlow();
5836 bool IsTableIStartMost() { return (mColIndex
== 0); }
5837 bool IsDamageAreaBStartMost() const {
5838 return mRowIndex
== mDamageArea
.StartRow();
5840 bool IsDamageAreaIEndMost() const {
5841 return mColIndex
>= mDamageArea
.EndCol();
5843 bool IsDamageAreaBEndMost() const {
5844 return mRowIndex
>= mDamageArea
.EndRow();
5846 bool IsDamageAreaIStartMost() const {
5847 return mColIndex
== mDamageArea
.StartCol();
5849 int32_t GetRelativeColIndex() const {
5850 return mColIndex
- mDamageArea
.StartCol();
5853 TableArea mDamageArea
; // damageArea in cellmap coordinates
5854 bool IsAfterRepeatedHeader() {
5855 return !mIsRepeatedHeader
&& (mRowIndex
== (mRepeatedHeaderRowIndex
+ 1));
5857 bool StartRepeatedFooter() const {
5858 return mIsRepeatedFooter
&& mRowIndex
== mRgFirstRowIndex
&&
5859 mRowIndex
!= mDamageArea
.StartRow();
5862 nscoord mInitialOffsetI
; // offsetI of the first border with
5863 // respect to the table
5864 nscoord mInitialOffsetB
; // offsetB of the first border with
5865 // respect to the table
5866 nscoord mNextOffsetB
; // offsetB of the next segment
5867 // this array is used differently when
5868 // inline-dir and block-dir borders are drawn
5869 // When inline-dir border are drawn we cache
5870 // the column widths and the width of the
5871 // block-dir borders that arrive from bStart
5872 // When we draw block-dir borders we store
5873 // lengths and width for block-dir borders
5874 // before they are drawn while we move over
5875 // the columns in the damage area
5876 // It has one more elements than columns are
5878 UniquePtr
<BCBlockDirSeg
[]> mBlockDirInfo
;
5879 BCInlineDirSeg mInlineSeg
; // the inline-dir segment while we
5880 // move over the colums
5881 BCPixelSize mPrevInlineSegBSize
; // the bSize of the previous
5882 // inline-dir border
5885 bool SetNewRow(nsTableRowFrame
* aRow
= nullptr);
5886 bool SetNewRowGroup();
5887 void SetNewData(int32_t aRowIndex
, int32_t aColIndex
);
5890 BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame
* aTable
)
5892 mTableFirstInFlow(static_cast<nsTableFrame
*>(aTable
->FirstInFlow())),
5893 mTableCellMap(aTable
->GetCellMap()),
5895 mTableWM(aTable
->Style()),
5896 mRowGroups(aTable
->OrderedRowGroups()),
5899 mIsRepeatedHeader(false),
5900 mIsRepeatedFooter(false),
5903 mFifRgFirstRowIndex(0),
5904 mRgFirstRowIndex(0),
5915 mPrevCellData(nullptr),
5920 mPrevInlineSegBSize(0) {
5921 MOZ_ASSERT(mTable
->IsBorderCollapse(),
5922 "Why are we here if the table is not border-collapsed?");
5924 const LogicalMargin bp
= mTable
->GetIncludedOuterBCBorder(mTableWM
);
5925 // block position of first row in damage area
5926 mInitialOffsetB
= mTable
->GetPrevInFlow() ? 0 : bp
.BStart(mTableWM
);
5927 mNumTableRows
= mTable
->GetRowCount();
5928 mNumTableCols
= mTable
->GetColCount();
5930 // initialize to a non existing index
5931 mRepeatedHeaderRowIndex
= -99;
5934 bool BCPaintBorderIterator::SetDamageArea(const nsRect
& aDirtyRect
) {
5935 nsSize containerSize
= mTable
->GetSize();
5936 LogicalRect
dirtyRect(mTableWM
, aDirtyRect
, containerSize
);
5937 uint32_t startRowIndex
, endRowIndex
, startColIndex
, endColIndex
;
5938 startRowIndex
= endRowIndex
= startColIndex
= endColIndex
= 0;
5940 bool haveIntersect
= false;
5941 // find startRowIndex, endRowIndex
5942 nscoord rowB
= mInitialOffsetB
;
5943 nsPresContext
* presContext
= mTable
->PresContext();
5944 for (uint32_t rgIdx
= 0; rgIdx
< mRowGroups
.Length() && !done
; rgIdx
++) {
5945 nsTableRowGroupFrame
* rgFrame
= mRowGroups
[rgIdx
];
5946 for (nsTableRowFrame
* rowFrame
= rgFrame
->GetFirstRow(); rowFrame
;
5947 rowFrame
= rowFrame
->GetNextRow()) {
5948 // get the row rect relative to the table rather than the row group
5949 nscoord rowBSize
= rowFrame
->BSize(mTableWM
);
5950 if (haveIntersect
) {
5951 // conservatively estimate the half border widths outside the row
5952 nscoord borderHalf
= mTable
->GetPrevInFlow()
5954 : presContext
->DevPixelsToAppUnits(
5955 rowFrame
->GetBStartBCBorderWidth() + 1);
5957 if (dirtyRect
.BEnd(mTableWM
) >= rowB
- borderHalf
) {
5958 nsTableRowFrame
* fifRow
=
5959 static_cast<nsTableRowFrame
*>(rowFrame
->FirstInFlow());
5960 endRowIndex
= fifRow
->GetRowIndex();
5964 // conservatively estimate the half border widths outside the row
5965 nscoord borderHalf
= mTable
->GetNextInFlow()
5967 : presContext
->DevPixelsToAppUnits(
5968 rowFrame
->GetBEndBCBorderWidth() + 1);
5969 if (rowB
+ rowBSize
+ borderHalf
>= dirtyRect
.BStart(mTableWM
)) {
5971 mStartRow
= rowFrame
;
5972 nsTableRowFrame
* fifRow
=
5973 static_cast<nsTableRowFrame
*>(rowFrame
->FirstInFlow());
5974 startRowIndex
= endRowIndex
= fifRow
->GetRowIndex();
5975 haveIntersect
= true;
5977 mInitialOffsetB
+= rowBSize
;
5983 mNextOffsetB
= mInitialOffsetB
;
5985 // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag
5986 // XXX but I don't understand it, so not changing it for now
5987 // table wrapper borders overflow the table, so the table might be
5988 // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set
5990 if (!haveIntersect
) return false;
5991 // find startColIndex, endColIndex, startColX
5992 haveIntersect
= false;
5993 if (0 == mNumTableCols
) return false;
5995 LogicalMargin bp
= mTable
->GetIncludedOuterBCBorder(mTableWM
);
5997 // inline position of first col in damage area
5998 mInitialOffsetI
= bp
.IStart(mTableWM
);
6002 for (colIdx
= 0; colIdx
!= mNumTableCols
; colIdx
++) {
6003 nsTableColFrame
* colFrame
= mTableFirstInFlow
->GetColFrame(colIdx
);
6004 if (!colFrame
) ABORT1(false);
6005 // get the col rect relative to the table rather than the col group
6006 nscoord colISize
= colFrame
->ISize(mTableWM
);
6007 if (haveIntersect
) {
6008 // conservatively estimate the iStart half border width outside the col
6009 nscoord iStartBorderHalf
= presContext
->DevPixelsToAppUnits(
6010 colFrame
->GetIStartBorderWidth() + 1);
6011 if (dirtyRect
.IEnd(mTableWM
) >= x
- iStartBorderHalf
) {
6012 endColIndex
= colIdx
;
6016 // conservatively estimate the iEnd half border width outside the col
6017 nscoord iEndBorderHalf
=
6018 presContext
->DevPixelsToAppUnits(colFrame
->GetIEndBorderWidth() + 1);
6019 if (x
+ colISize
+ iEndBorderHalf
>= dirtyRect
.IStart(mTableWM
)) {
6020 startColIndex
= endColIndex
= colIdx
;
6021 haveIntersect
= true;
6023 mInitialOffsetI
+= colISize
;
6028 if (!haveIntersect
) return false;
6030 TableArea(startColIndex
, startRowIndex
,
6031 1 + DeprecatedAbs
<int32_t>(endColIndex
- startColIndex
),
6032 1 + endRowIndex
- startRowIndex
);
6035 mBlockDirInfo
= MakeUnique
<BCBlockDirSeg
[]>(mDamageArea
.ColCount() + 1);
6039 void BCPaintBorderIterator::Reset() {
6040 mAtEnd
= true; // gets reset when First() is called
6047 mPrevCell
= nullptr;
6049 mPrevCellData
= nullptr;
6050 mCellData
= nullptr;
6056 * Set the iterator data to a new cellmap coordinate
6057 * @param aRowIndex - the row index
6058 * @param aColIndex - the col index
6060 void BCPaintBorderIterator::SetNewData(int32_t aY
, int32_t aX
) {
6061 if (!mTableCellMap
|| !mTableCellMap
->mBCInfo
) ABORT0();
6065 mPrevCellData
= mCellData
;
6066 if (IsTableIEndMost() && IsTableBEndMost()) {
6068 mBCData
= &mTableCellMap
->mBCInfo
->mBEndIEndCorner
;
6069 } else if (IsTableIEndMost()) {
6070 mCellData
= nullptr;
6071 mBCData
= &mTableCellMap
->mBCInfo
->mIEndBorders
.ElementAt(aY
);
6072 } else if (IsTableBEndMost()) {
6073 mCellData
= nullptr;
6074 mBCData
= &mTableCellMap
->mBCInfo
->mBEndBorders
.ElementAt(aX
);
6076 // We should have set mCellMap during SetNewRowGroup, but if we failed to
6077 // find the appropriate map there, let's just give up.
6078 // Bailing out here may leave us with some missing borders, but seems
6079 // preferable to crashing. (Bug 1442018)
6080 if (MOZ_UNLIKELY(!mCellMap
)) {
6083 if (uint32_t(mRowIndex
- mFifRgFirstRowIndex
) < mCellMap
->mRows
.Length()) {
6085 mCellData
= (BCCellData
*)mCellMap
->mRows
[mRowIndex
- mFifRgFirstRowIndex
]
6086 .SafeElementAt(mColIndex
);
6088 mBCData
= &mCellData
->mData
;
6089 if (!mCellData
->IsOrig()) {
6090 if (mCellData
->IsRowSpan()) {
6091 aY
-= mCellData
->GetRowSpanOffset();
6093 if (mCellData
->IsColSpan()) {
6094 aX
-= mCellData
->GetColSpanOffset();
6096 if ((aX
>= 0) && (aY
>= 0)) {
6098 (BCCellData
*)mCellMap
->mRows
[aY
- mFifRgFirstRowIndex
][aX
];
6101 if (mCellData
->IsOrig()) {
6103 mCell
= mCellData
->GetCellFrame();
6111 * Set the iterator to a new row
6112 * @param aRow - the new row frame, if null the iterator will advance to the
6115 bool BCPaintBorderIterator::SetNewRow(nsTableRowFrame
* aRow
) {
6117 mRow
= (aRow
) ? aRow
: mRow
->GetNextRow();
6120 mRowIndex
= mRow
->GetRowIndex();
6121 mColIndex
= mDamageArea
.StartCol();
6122 mPrevInlineSegBSize
= 0;
6123 if (mIsRepeatedHeader
) {
6124 mRepeatedHeaderRowIndex
= mRowIndex
;
6133 * Advance the iterator to the next row group
6135 bool BCPaintBorderIterator::SetNewRowGroup() {
6138 mIsRepeatedHeader
= false;
6139 mIsRepeatedFooter
= false;
6141 NS_ASSERTION(mRgIndex
>= 0, "mRgIndex out of bounds");
6142 if (uint32_t(mRgIndex
) < mRowGroups
.Length()) {
6144 mRg
= mRowGroups
[mRgIndex
];
6145 nsTableRowGroupFrame
* fifRg
=
6146 static_cast<nsTableRowGroupFrame
*>(mRg
->FirstInFlow());
6147 mFifRgFirstRowIndex
= fifRg
->GetStartRowIndex();
6148 mRgFirstRowIndex
= mRg
->GetStartRowIndex();
6149 mRgLastRowIndex
= mRgFirstRowIndex
+ mRg
->GetRowCount() - 1;
6151 if (SetNewRow(mRg
->GetFirstRow())) {
6152 mCellMap
= mTableCellMap
->GetMapFor(fifRg
, nullptr);
6153 if (!mCellMap
) ABORT1(false);
6155 if (mTable
->GetPrevInFlow() && !mRg
->GetPrevInFlow()) {
6156 // if mRowGroup doesn't have a prev in flow, then it may be a repeated
6158 const nsStyleDisplay
* display
= mRg
->StyleDisplay();
6159 if (mRowIndex
== mDamageArea
.StartRow()) {
6161 (mozilla::StyleDisplay::TableHeaderGroup
== display
->mDisplay
);
6164 (mozilla::StyleDisplay::TableFooterGroup
== display
->mDisplay
);
6174 * Move the iterator to the first position in the damageArea
6176 void BCPaintBorderIterator::First() {
6177 if (!mTable
|| mDamageArea
.StartCol() >= mNumTableCols
||
6178 mDamageArea
.StartRow() >= mNumTableRows
)
6183 uint32_t numRowGroups
= mRowGroups
.Length();
6184 for (uint32_t rgY
= 0; rgY
< numRowGroups
; rgY
++) {
6185 nsTableRowGroupFrame
* rowG
= mRowGroups
[rgY
];
6186 int32_t start
= rowG
->GetStartRowIndex();
6187 int32_t end
= start
+ rowG
->GetRowCount() - 1;
6188 if (mDamageArea
.StartRow() >= start
&& mDamageArea
.StartRow() <= end
) {
6189 mRgIndex
= rgY
- 1; // SetNewRowGroup increments rowGroupIndex
6190 if (SetNewRowGroup()) {
6191 while (mRowIndex
< mDamageArea
.StartRow() && !mAtEnd
) {
6195 SetNewData(mDamageArea
.StartRow(), mDamageArea
.StartCol());
6205 * Advance the iterator to the next position
6207 void BCPaintBorderIterator::Next() {
6208 if (mAtEnd
) ABORT0();
6212 if (mColIndex
> mDamageArea
.EndCol()) {
6214 if (mRowIndex
== mDamageArea
.EndRow()) {
6215 mColIndex
= mDamageArea
.StartCol();
6216 } else if (mRowIndex
< mDamageArea
.EndRow()) {
6217 if (mRowIndex
<= mRgLastRowIndex
) {
6227 SetNewData(mRowIndex
, mColIndex
);
6231 // XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine
6233 // XXX Update terminology from physical to logical
6234 /** Compute the vertical offset of a vertical border segment
6235 * @param aCornerOwnerSide - which side owns the corner
6236 * @param aCornerSubWidth - how wide is the nonwinning side of the corner
6237 * @param aHorWidth - how wide is the horizontal edge of the corner
6238 * @param aIsStartOfSeg - does this corner start a new segment
6239 * @param aIsBevel - is this corner beveled
6240 * @return - offset in twips
6242 static nscoord
CalcVerCornerOffset(nsPresContext
* aPresContext
,
6243 LogicalSide aCornerOwnerSide
,
6244 BCPixelSize aCornerSubWidth
,
6245 BCPixelSize aHorWidth
, bool aIsStartOfSeg
,
6248 // XXX These should be replaced with appropriate side-specific macros (which?)
6249 BCPixelSize smallHalf
, largeHalf
;
6250 if (IsBlock(aCornerOwnerSide
)) {
6251 DivideBCBorderSize(aCornerSubWidth
, smallHalf
, largeHalf
);
6253 offset
= (aIsStartOfSeg
) ? -largeHalf
: smallHalf
;
6256 (LogicalSide::BStart
== aCornerOwnerSide
) ? smallHalf
: -largeHalf
;
6259 DivideBCBorderSize(aHorWidth
, smallHalf
, largeHalf
);
6261 offset
= (aIsStartOfSeg
) ? -largeHalf
: smallHalf
;
6263 offset
= (aIsStartOfSeg
) ? smallHalf
: -largeHalf
;
6266 return aPresContext
->DevPixelsToAppUnits(offset
);
6269 /** Compute the horizontal offset of a horizontal border segment
6270 * @param aCornerOwnerSide - which side owns the corner
6271 * @param aCornerSubWidth - how wide is the nonwinning side of the corner
6272 * @param aVerWidth - how wide is the vertical edge of the corner
6273 * @param aIsStartOfSeg - does this corner start a new segment
6274 * @param aIsBevel - is this corner beveled
6275 * @return - offset in twips
6277 static nscoord
CalcHorCornerOffset(nsPresContext
* aPresContext
,
6278 LogicalSide aCornerOwnerSide
,
6279 BCPixelSize aCornerSubWidth
,
6280 BCPixelSize aVerWidth
, bool aIsStartOfSeg
,
6283 // XXX These should be replaced with appropriate side-specific macros (which?)
6284 BCPixelSize smallHalf
, largeHalf
;
6285 if (IsInline(aCornerOwnerSide
)) {
6286 DivideBCBorderSize(aCornerSubWidth
, smallHalf
, largeHalf
);
6288 offset
= (aIsStartOfSeg
) ? -largeHalf
: smallHalf
;
6291 (LogicalSide::IStart
== aCornerOwnerSide
) ? smallHalf
: -largeHalf
;
6294 DivideBCBorderSize(aVerWidth
, smallHalf
, largeHalf
);
6296 offset
= (aIsStartOfSeg
) ? -largeHalf
: smallHalf
;
6298 offset
= (aIsStartOfSeg
) ? smallHalf
: -largeHalf
;
6301 return aPresContext
->DevPixelsToAppUnits(offset
);
6304 BCBlockDirSeg::BCBlockDirSeg()
6305 : mFirstRowGroup(nullptr),
6307 mBEndInlineSegBSize(0),
6309 mIsBEndBevel(false) {
6311 mFirstCell
= mLastCell
= mAjaCell
= nullptr;
6312 mOffsetI
= mOffsetB
= mLength
= mWidth
= mBStartBevelOffset
= 0;
6313 mBStartBevelSide
= LogicalSide::BStart
;
6314 mOwner
= eCellOwner
;
6318 * Start a new block-direction segment
6319 * @param aIter - iterator containing the structural information
6320 * @param aBorderOwner - determines the border style
6321 * @param aBlockSegISize - the width of segment in pixel
6322 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6323 * corner at the start
6325 void BCBlockDirSeg::Start(BCPaintBorderIterator
& aIter
,
6326 BCBorderOwner aBorderOwner
,
6327 BCPixelSize aBlockSegISize
,
6328 BCPixelSize aInlineSegBSize
,
6329 Maybe
<nscoord
> aEmptyRowEndBSize
) {
6330 LogicalSide ownerSide
= LogicalSide::BStart
;
6333 nscoord cornerSubWidth
=
6334 (aIter
.mBCData
) ? aIter
.mBCData
->GetCorner(ownerSide
, bevel
) : 0;
6336 bool bStartBevel
= (aBlockSegISize
> 0) ? bevel
: false;
6337 BCPixelSize maxInlineSegBSize
=
6338 std::max(aIter
.mPrevInlineSegBSize
, aInlineSegBSize
);
6339 nsPresContext
* presContext
= aIter
.mTable
->PresContext();
6340 nscoord offset
= CalcVerCornerOffset(presContext
, ownerSide
, cornerSubWidth
,
6341 maxInlineSegBSize
, true, bStartBevel
);
6343 mBStartBevelOffset
=
6344 bStartBevel
? presContext
->DevPixelsToAppUnits(maxInlineSegBSize
) : 0;
6345 // XXX this assumes that only corners where 2 segments join can be beveled
6347 (aInlineSegBSize
> 0) ? LogicalSide::IEnd
: LogicalSide::IStart
;
6348 if (aEmptyRowEndBSize
&& *aEmptyRowEndBSize
< offset
) {
6349 // This segment is starting from an empty row. This will require the the
6350 // starting segment to overlap with the previously drawn segment, unless the
6351 // empty row's size clears the overlap.
6352 mOffsetB
+= *aEmptyRowEndBSize
;
6357 mWidth
= aBlockSegISize
;
6358 mOwner
= aBorderOwner
;
6359 mFirstCell
= aIter
.mCell
;
6360 mFirstRowGroup
= aIter
.mRg
;
6361 mFirstRow
= aIter
.mRow
;
6362 if (aIter
.GetRelativeColIndex() > 0) {
6363 mAjaCell
= aIter
.mBlockDirInfo
[aIter
.GetRelativeColIndex() - 1].mLastCell
;
6368 * Initialize the block-dir segments with information that will persist for any
6369 * block-dir segment in this column
6370 * @param aIter - iterator containing the structural information
6372 void BCBlockDirSeg::Initialize(BCPaintBorderIterator
& aIter
) {
6373 int32_t relColIndex
= aIter
.GetRelativeColIndex();
6374 mCol
= aIter
.IsTableIEndMost()
6375 ? aIter
.mBlockDirInfo
[relColIndex
- 1].mCol
6376 : aIter
.mTableFirstInFlow
->GetColFrame(aIter
.mColIndex
);
6377 if (!mCol
) ABORT0();
6378 if (0 == relColIndex
) {
6379 mOffsetI
= aIter
.mInitialOffsetI
;
6381 // set mOffsetI for the next column
6382 if (!aIter
.IsDamageAreaIEndMost()) {
6383 aIter
.mBlockDirInfo
[relColIndex
+ 1].mOffsetI
=
6384 mOffsetI
+ mCol
->ISize(aIter
.mTableWM
);
6386 mOffsetB
= aIter
.mInitialOffsetB
;
6387 mLastCell
= aIter
.mCell
;
6391 * Compute the offsets for the bEnd corner of a block-dir segment
6392 * @param aIter - iterator containing the structural information
6393 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6394 * corner at the start
6396 void BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator
& aIter
,
6397 BCPixelSize aInlineSegBSize
) {
6398 LogicalSide ownerSide
= LogicalSide::BStart
;
6399 nscoord cornerSubWidth
= 0;
6401 if (aIter
.mBCData
) {
6402 cornerSubWidth
= aIter
.mBCData
->GetCorner(ownerSide
, bevel
);
6404 mIsBEndBevel
= (mWidth
> 0) ? bevel
: false;
6405 mBEndInlineSegBSize
= std::max(aIter
.mPrevInlineSegBSize
, aInlineSegBSize
);
6406 mBEndOffset
= CalcVerCornerOffset(aIter
.mTable
->PresContext(), ownerSide
,
6407 cornerSubWidth
, mBEndInlineSegBSize
, false,
6409 mLength
+= mBEndOffset
;
6412 Maybe
<BCBorderParameters
> BCBlockDirSeg::BuildBorderParameters(
6413 BCPaintBorderIterator
& aIter
, BCPixelSize aInlineSegBSize
) {
6414 BCBorderParameters result
;
6416 // get the border style, color and paint the segment
6418 aIter
.IsDamageAreaIEndMost() ? LogicalSide::IEnd
: LogicalSide::IStart
;
6419 int32_t relColIndex
= aIter
.GetRelativeColIndex();
6420 nsTableColFrame
* col
= mCol
;
6421 if (!col
) ABORT1(Nothing());
6422 nsTableCellFrame
* cell
= mFirstCell
; // ???
6423 nsIFrame
* owner
= nullptr;
6424 result
.mBorderStyle
= StyleBorderStyle::Solid
;
6425 result
.mBorderColor
= 0xFFFFFFFF;
6426 result
.mBackfaceIsVisible
= true;
6428 // All the tables frames have the same presContext, so we just use any one
6429 // that exists here:
6430 nsPresContext
* presContext
= aIter
.mTable
->PresContext();
6431 result
.mAppUnitsPerDevPixel
= presContext
->AppUnitsPerDevPixel();
6435 owner
= aIter
.mTable
;
6437 case eAjaColGroupOwner
:
6438 side
= LogicalSide::IEnd
;
6439 if (!aIter
.IsTableIEndMost() && (relColIndex
> 0)) {
6440 col
= aIter
.mBlockDirInfo
[relColIndex
- 1].mCol
;
6443 case eColGroupOwner
:
6445 owner
= col
->GetParent();
6449 side
= LogicalSide::IEnd
;
6450 if (!aIter
.IsTableIEndMost() && (relColIndex
> 0)) {
6451 col
= aIter
.mBlockDirInfo
[relColIndex
- 1].mCol
;
6457 case eAjaRowGroupOwner
:
6458 NS_ERROR("a neighboring rowgroup can never own a vertical border");
6460 case eRowGroupOwner
:
6461 NS_ASSERTION(aIter
.IsTableIStartMost() || aIter
.IsTableIEndMost(),
6462 "row group can own border only at table edge");
6463 owner
= mFirstRowGroup
;
6466 NS_ERROR("program error");
6469 NS_ASSERTION(aIter
.IsTableIStartMost() || aIter
.IsTableIEndMost(),
6470 "row can own border only at table edge");
6474 side
= LogicalSide::IEnd
;
6482 ::GetPaintStyleInfo(owner
, aIter
.mTableWM
, side
, &result
.mBorderStyle
,
6483 &result
.mBorderColor
);
6484 result
.mBackfaceIsVisible
= !owner
->BackfaceIsHidden();
6486 BCPixelSize smallHalf
, largeHalf
;
6487 DivideBCBorderSize(mWidth
, smallHalf
, largeHalf
);
6488 LogicalRect
segRect(
6489 aIter
.mTableWM
, mOffsetI
- presContext
->DevPixelsToAppUnits(largeHalf
),
6490 mOffsetB
, presContext
->DevPixelsToAppUnits(mWidth
), mLength
);
6491 nscoord bEndBevelOffset
=
6492 (mIsBEndBevel
) ? presContext
->DevPixelsToAppUnits(mBEndInlineSegBSize
)
6494 LogicalSide bEndBevelSide
=
6495 (aInlineSegBSize
> 0) ? LogicalSide::IEnd
: LogicalSide::IStart
;
6497 // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
6499 result
.mBorderRect
=
6500 segRect
.GetPhysicalRect(aIter
.mTableWM
, aIter
.mTable
->GetSize());
6501 // XXX For reversed vertical writing-modes (with direction:rtl), we need to
6502 // invert physicalRect's y-position here, with respect to the table.
6503 // However, it's not worth fixing the border positions here until the
6504 // ordering of the table columns themselves is also fixed (bug 1180528).
6506 result
.mStartBevelSide
= aIter
.mTableWM
.PhysicalSide(mBStartBevelSide
);
6507 result
.mEndBevelSide
= aIter
.mTableWM
.PhysicalSide(bEndBevelSide
);
6508 result
.mStartBevelOffset
= mBStartBevelOffset
;
6509 result
.mEndBevelOffset
= bEndBevelOffset
;
6510 // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal)
6511 // border segment need to be swapped because DrawTableBorderSegment will
6512 // apply the 'start' bevel at the left edge, and 'end' at the right.
6513 // (Note: In this case, startBevelSide/endBevelSide will usually both be
6514 // "top" or "bottom". DrawTableBorderSegment works purely with physical
6515 // coordinates, so it expects startBevelOffset to be the indentation-from-
6516 // the-left for the "start" (left) end of the border-segment, and
6517 // endBevelOffset is the indentation-from-the-right for the "end" (right)
6518 // end of the border-segment. We've got them reversed, since our block dir
6519 // is RTL, so we have to swap them here.)
6520 if (aIter
.mTableWM
.IsVerticalRL()) {
6521 std::swap(result
.mStartBevelSide
, result
.mEndBevelSide
);
6522 std::swap(result
.mStartBevelOffset
, result
.mEndBevelOffset
);
6525 return Some(result
);
6529 * Paint the block-dir segment
6530 * @param aIter - iterator containing the structural information
6531 * @param aDrawTarget - the draw target
6532 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6533 * corner at the start
6535 void BCBlockDirSeg::Paint(BCPaintBorderIterator
& aIter
, DrawTarget
& aDrawTarget
,
6536 BCPixelSize aInlineSegBSize
) {
6537 Maybe
<BCBorderParameters
> param
=
6538 BuildBorderParameters(aIter
, aInlineSegBSize
);
6539 if (param
.isNothing()) {
6543 nsCSSRendering::DrawTableBorderSegment(
6544 aDrawTarget
, param
->mBorderStyle
, param
->mBorderColor
, param
->mBorderRect
,
6545 param
->mAppUnitsPerDevPixel
, param
->mStartBevelSide
,
6546 param
->mStartBevelOffset
, param
->mEndBevelSide
, param
->mEndBevelOffset
);
6549 // Pushes a border bevel triangle and substracts the relevant rectangle from
6550 // aRect, which, after all the bevels, will end up being a solid segment rect.
6551 static void AdjustAndPushBevel(wr::DisplayListBuilder
& aBuilder
,
6552 wr::LayoutRect
& aRect
, nscolor aColor
,
6553 const nsCSSRendering::Bevel
& aBevel
,
6554 int32_t aAppUnitsPerDevPixel
,
6555 bool aBackfaceIsVisible
, bool aIsStart
) {
6556 if (!aBevel
.mOffset
) {
6560 const auto kTransparent
= wr::ToColorF(gfx::DeviceColor(0., 0., 0., 0.));
6561 const bool horizontal
=
6562 aBevel
.mSide
== eSideTop
|| aBevel
.mSide
== eSideBottom
;
6564 // Crappy CSS triangle as known by every web developer ever :)
6565 Float offset
= NSAppUnitsToFloatPixels(aBevel
.mOffset
, aAppUnitsPerDevPixel
);
6566 wr::LayoutRect bevelRect
= aRect
;
6567 wr::BorderSide bevelBorder
[4];
6568 for (const auto i
: mozilla::AllPhysicalSides()) {
6570 wr::ToBorderSide(ToDeviceColor(aColor
), StyleBorderStyle::Solid
);
6573 // We're creating a half-transparent triangle using the border primitive.
6575 // Classic web-dev trick, with a gotcha: we use a single corner to avoid
6576 // seams and rounding errors.
6578 // Classic web-dev trick :P
6579 auto borderWidths
= wr::ToBorderWidths(0, 0, 0, 0);
6580 bevelBorder
[aBevel
.mSide
].color
= kTransparent
;
6583 bevelBorder
[eSideLeft
].color
= kTransparent
;
6584 borderWidths
.left
= offset
;
6586 bevelBorder
[eSideTop
].color
= kTransparent
;
6587 borderWidths
.top
= offset
;
6591 bevelBorder
[eSideRight
].color
= kTransparent
;
6592 borderWidths
.right
= offset
;
6594 bevelBorder
[eSideBottom
].color
= kTransparent
;
6595 borderWidths
.bottom
= offset
;
6601 aRect
.min
.x
+= offset
;
6602 aRect
.max
.x
+= offset
;
6604 bevelRect
.min
.x
+= aRect
.width() - offset
;
6605 bevelRect
.max
.x
+= aRect
.width() - offset
;
6607 aRect
.max
.x
-= offset
;
6608 bevelRect
.max
.y
= bevelRect
.min
.y
+ aRect
.height();
6609 bevelRect
.max
.x
= bevelRect
.min
.x
+ offset
;
6610 if (aBevel
.mSide
== eSideTop
) {
6611 borderWidths
.bottom
= aRect
.height();
6613 borderWidths
.top
= aRect
.height();
6617 aRect
.min
.y
+= offset
;
6618 aRect
.max
.y
+= offset
;
6620 bevelRect
.min
.y
+= aRect
.height() - offset
;
6621 bevelRect
.max
.y
+= aRect
.height() - offset
;
6623 aRect
.max
.y
-= offset
;
6624 bevelRect
.max
.x
= bevelRect
.min
.x
+ aRect
.width();
6625 bevelRect
.max
.y
= bevelRect
.min
.y
+ offset
;
6626 if (aBevel
.mSide
== eSideLeft
) {
6627 borderWidths
.right
= aRect
.width();
6629 borderWidths
.left
= aRect
.width();
6633 Range
<const wr::BorderSide
> wrsides(bevelBorder
, 4);
6634 // It's important to _not_ anti-alias the bevel, because otherwise we wouldn't
6635 // be able bevel to sides of the same color without bleeding in the middle.
6636 aBuilder
.PushBorder(bevelRect
, bevelRect
, aBackfaceIsVisible
, borderWidths
,
6637 wrsides
, wr::EmptyBorderRadius(),
6638 wr::AntialiasBorder::No
);
6641 static void CreateWRCommandsForBeveledBorder(
6642 const BCBorderParameters
& aBorderParams
, wr::DisplayListBuilder
& aBuilder
,
6643 const layers::StackingContextHelper
& aSc
, const nsPoint
& aOffset
) {
6644 MOZ_ASSERT(aBorderParams
.NeedToBevel());
6646 AutoTArray
<nsCSSRendering::SolidBeveledBorderSegment
, 3> segments
;
6647 nsCSSRendering::GetTableBorderSolidSegments(
6648 segments
, aBorderParams
.mBorderStyle
, aBorderParams
.mBorderColor
,
6649 aBorderParams
.mBorderRect
, aBorderParams
.mAppUnitsPerDevPixel
,
6650 aBorderParams
.mStartBevelSide
, aBorderParams
.mStartBevelOffset
,
6651 aBorderParams
.mEndBevelSide
, aBorderParams
.mEndBevelOffset
);
6653 for (const auto& segment
: segments
) {
6654 auto rect
= LayoutDeviceRect::FromUnknownRect(NSRectToRect(
6655 segment
.mRect
+ aOffset
, aBorderParams
.mAppUnitsPerDevPixel
));
6656 auto r
= wr::ToLayoutRect(rect
);
6657 auto color
= wr::ToColorF(ToDeviceColor(segment
.mColor
));
6659 // Adjust for the start bevel if needed.
6660 AdjustAndPushBevel(aBuilder
, r
, segment
.mColor
, segment
.mStartBevel
,
6661 aBorderParams
.mAppUnitsPerDevPixel
,
6662 aBorderParams
.mBackfaceIsVisible
, true);
6664 AdjustAndPushBevel(aBuilder
, r
, segment
.mColor
, segment
.mEndBevel
,
6665 aBorderParams
.mAppUnitsPerDevPixel
,
6666 aBorderParams
.mBackfaceIsVisible
, false);
6668 aBuilder
.PushRect(r
, r
, aBorderParams
.mBackfaceIsVisible
, false, false,
6673 static void CreateWRCommandsForBorderSegment(
6674 const BCBorderParameters
& aBorderParams
, wr::DisplayListBuilder
& aBuilder
,
6675 const layers::StackingContextHelper
& aSc
, const nsPoint
& aOffset
) {
6676 if (aBorderParams
.NeedToBevel()) {
6677 CreateWRCommandsForBeveledBorder(aBorderParams
, aBuilder
, aSc
, aOffset
);
6681 auto borderRect
= LayoutDeviceRect::FromUnknownRect(NSRectToRect(
6682 aBorderParams
.mBorderRect
+ aOffset
, aBorderParams
.mAppUnitsPerDevPixel
));
6684 wr::LayoutRect r
= wr::ToLayoutRect(borderRect
);
6685 wr::BorderSide wrSide
[4];
6686 for (const auto i
: mozilla::AllPhysicalSides()) {
6687 wrSide
[i
] = wr::ToBorderSide(ToDeviceColor(aBorderParams
.mBorderColor
),
6688 StyleBorderStyle::None
);
6690 const bool horizontal
= aBorderParams
.mStartBevelSide
== eSideTop
||
6691 aBorderParams
.mStartBevelSide
== eSideBottom
;
6692 auto borderWidth
= horizontal
? r
.height() : r
.width();
6694 // All border style is set to none except left side. So setting the widths of
6695 // each side to width of rect is fine.
6696 auto borderWidths
= wr::ToBorderWidths(0, 0, 0, 0);
6698 wrSide
[horizontal
? eSideTop
: eSideLeft
] = wr::ToBorderSide(
6699 ToDeviceColor(aBorderParams
.mBorderColor
), aBorderParams
.mBorderStyle
);
6702 borderWidths
.top
= borderWidth
;
6704 borderWidths
.left
= borderWidth
;
6707 Range
<const wr::BorderSide
> wrsides(wrSide
, 4);
6708 aBuilder
.PushBorder(r
, r
, aBorderParams
.mBackfaceIsVisible
, borderWidths
,
6709 wrsides
, wr::EmptyBorderRadius());
6712 void BCBlockDirSeg::CreateWebRenderCommands(
6713 BCPaintBorderIterator
& aIter
, BCPixelSize aInlineSegBSize
,
6714 wr::DisplayListBuilder
& aBuilder
, const layers::StackingContextHelper
& aSc
,
6715 const nsPoint
& aOffset
) {
6716 Maybe
<BCBorderParameters
> param
=
6717 BuildBorderParameters(aIter
, aInlineSegBSize
);
6718 if (param
.isNothing()) {
6722 CreateWRCommandsForBorderSegment(*param
, aBuilder
, aSc
, aOffset
);
6726 * Advance the start point of a segment
6728 void BCBlockDirSeg::AdvanceOffsetB() { mOffsetB
+= mLength
- mBEndOffset
; }
6731 * Accumulate the current segment
6733 void BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator
& aIter
) {
6734 mLastCell
= aIter
.mCell
;
6735 mLength
+= aIter
.mRow
->BSize(aIter
.mTableWM
);
6738 BCInlineDirSeg::BCInlineDirSeg()
6739 : mIsIEndBevel(false),
6740 mIEndBevelOffset(0),
6741 mIEndBevelSide(LogicalSide::BStart
),
6743 mOwner(eTableOwner
) {
6744 mOffsetI
= mOffsetB
= mLength
= mWidth
= mIStartBevelOffset
= 0;
6745 mIStartBevelSide
= LogicalSide::BStart
;
6746 mFirstCell
= mAjaCell
= nullptr;
6749 /** Initialize an inline-dir border segment for painting
6750 * @param aIter - iterator storing the current and adjacent frames
6751 * @param aBorderOwner - which frame owns the border
6752 * @param aBEndBlockSegISize - block-dir segment width coming from up
6753 * @param aInlineSegBSize - the thickness of the segment
6755 void BCInlineDirSeg::Start(BCPaintBorderIterator
& aIter
,
6756 BCBorderOwner aBorderOwner
,
6757 BCPixelSize aBEndBlockSegISize
,
6758 BCPixelSize aInlineSegBSize
) {
6759 LogicalSide cornerOwnerSide
= LogicalSide::BStart
;
6762 mOwner
= aBorderOwner
;
6763 nscoord cornerSubWidth
=
6764 (aIter
.mBCData
) ? aIter
.mBCData
->GetCorner(cornerOwnerSide
, bevel
) : 0;
6766 bool iStartBevel
= (aInlineSegBSize
> 0) ? bevel
: false;
6767 int32_t relColIndex
= aIter
.GetRelativeColIndex();
6768 nscoord maxBlockSegISize
=
6769 std::max(aIter
.mBlockDirInfo
[relColIndex
].mWidth
, aBEndBlockSegISize
);
6771 CalcHorCornerOffset(aIter
.mTable
->PresContext(), cornerOwnerSide
,
6772 cornerSubWidth
, maxBlockSegISize
, true, iStartBevel
);
6773 mIStartBevelOffset
=
6774 (iStartBevel
&& (aInlineSegBSize
> 0)) ? maxBlockSegISize
: 0;
6775 // XXX this assumes that only corners where 2 segments join can be beveled
6777 (aBEndBlockSegISize
> 0) ? LogicalSide::BEnd
: LogicalSide::BStart
;
6780 mWidth
= aInlineSegBSize
;
6781 mFirstCell
= aIter
.mCell
;
6782 mAjaCell
= (aIter
.IsDamageAreaBStartMost())
6784 : aIter
.mBlockDirInfo
[relColIndex
].mLastCell
;
6788 * Compute the offsets for the iEnd corner of an inline-dir segment
6789 * @param aIter - iterator containing the structural information
6790 * @param aIStartSegISize - the iSize of the block-dir segment joining the
6791 * corner at the start
6793 void BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator
& aIter
,
6794 BCPixelSize aIStartSegISize
) {
6795 LogicalSide ownerSide
= LogicalSide::BStart
;
6796 nscoord cornerSubWidth
= 0;
6798 if (aIter
.mBCData
) {
6799 cornerSubWidth
= aIter
.mBCData
->GetCorner(ownerSide
, bevel
);
6802 mIsIEndBevel
= (mWidth
> 0) ? bevel
: 0;
6803 int32_t relColIndex
= aIter
.GetRelativeColIndex();
6805 std::max(aIter
.mBlockDirInfo
[relColIndex
].mWidth
, aIStartSegISize
);
6806 nsPresContext
* presContext
= aIter
.mTable
->PresContext();
6807 mEndOffset
= CalcHorCornerOffset(presContext
, ownerSide
, cornerSubWidth
,
6808 verWidth
, false, mIsIEndBevel
);
6809 mLength
+= mEndOffset
;
6811 (mIsIEndBevel
) ? presContext
->DevPixelsToAppUnits(verWidth
) : 0;
6813 (aIStartSegISize
> 0) ? LogicalSide::BEnd
: LogicalSide::BStart
;
6816 Maybe
<BCBorderParameters
> BCInlineDirSeg::BuildBorderParameters(
6817 BCPaintBorderIterator
& aIter
) {
6818 BCBorderParameters result
;
6820 // get the border style, color and paint the segment
6822 aIter
.IsDamageAreaBEndMost() ? LogicalSide::BEnd
: LogicalSide::BStart
;
6823 nsIFrame
* rg
= aIter
.mRg
;
6824 if (!rg
) ABORT1(Nothing());
6825 nsIFrame
* row
= aIter
.mRow
;
6826 if (!row
) ABORT1(Nothing());
6827 nsIFrame
* cell
= mFirstCell
;
6829 nsIFrame
* owner
= nullptr;
6830 result
.mBackfaceIsVisible
= true;
6832 // All the tables frames have the same presContext, so we just use any one
6833 // that exists here:
6834 nsPresContext
* presContext
= aIter
.mTable
->PresContext();
6835 result
.mAppUnitsPerDevPixel
= presContext
->AppUnitsPerDevPixel();
6837 result
.mBorderStyle
= StyleBorderStyle::Solid
;
6838 result
.mBorderColor
= 0xFFFFFFFF;
6842 owner
= aIter
.mTable
;
6844 case eAjaColGroupOwner
:
6845 NS_ERROR("neighboring colgroups can never own an inline-dir border");
6847 case eColGroupOwner
:
6848 NS_ASSERTION(aIter
.IsTableBStartMost() || aIter
.IsTableBEndMost(),
6849 "col group can own border only at the table edge");
6850 col
= aIter
.mTableFirstInFlow
->GetColFrame(aIter
.mColIndex
- 1);
6851 if (!col
) ABORT1(Nothing());
6852 owner
= col
->GetParent();
6855 NS_ERROR("neighboring column can never own an inline-dir border");
6858 NS_ASSERTION(aIter
.IsTableBStartMost() || aIter
.IsTableBEndMost(),
6859 "col can own border only at the table edge");
6860 owner
= aIter
.mTableFirstInFlow
->GetColFrame(aIter
.mColIndex
- 1);
6862 case eAjaRowGroupOwner
:
6863 side
= LogicalSide::BEnd
;
6864 rg
= (aIter
.IsTableBEndMost()) ? aIter
.mRg
: aIter
.mPrevRg
;
6866 case eRowGroupOwner
:
6870 side
= LogicalSide::BEnd
;
6871 row
= (aIter
.IsTableBEndMost()) ? aIter
.mRow
: aIter
.mPrevRow
;
6877 side
= LogicalSide::BEnd
;
6878 // if this is null due to the damage area origin-y > 0, then the border
6879 // won't show up anyway
6887 ::GetPaintStyleInfo(owner
, aIter
.mTableWM
, side
, &result
.mBorderStyle
,
6888 &result
.mBorderColor
);
6889 result
.mBackfaceIsVisible
= !owner
->BackfaceIsHidden();
6891 BCPixelSize smallHalf
, largeHalf
;
6892 DivideBCBorderSize(mWidth
, smallHalf
, largeHalf
);
6893 LogicalRect
segRect(aIter
.mTableWM
, mOffsetI
,
6894 mOffsetB
- presContext
->DevPixelsToAppUnits(largeHalf
),
6895 mLength
, presContext
->DevPixelsToAppUnits(mWidth
));
6897 // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
6898 result
.mBorderRect
=
6899 segRect
.GetPhysicalRect(aIter
.mTableWM
, aIter
.mTable
->GetSize());
6900 result
.mStartBevelSide
= aIter
.mTableWM
.PhysicalSide(mIStartBevelSide
);
6901 result
.mEndBevelSide
= aIter
.mTableWM
.PhysicalSide(mIEndBevelSide
);
6902 result
.mStartBevelOffset
=
6903 presContext
->DevPixelsToAppUnits(mIStartBevelOffset
);
6904 result
.mEndBevelOffset
= mIEndBevelOffset
;
6905 // With inline-RTL directionality, the 'start' and 'end' of the inline-dir
6906 // border segment need to be swapped because DrawTableBorderSegment will
6907 // apply the 'start' bevel physically at the left or top edge, and 'end' at
6908 // the right or bottom.
6909 // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal
6910 // writing mode, or "left" or "right" in vertical mode.
6911 // DrawTableBorderSegment works purely with physical coordinates, so it
6912 // expects startBevelOffset to be the indentation-from-the-left or top end
6913 // of the border-segment, and endBevelOffset is the indentation-from-the-
6914 // right or bottom end. If the writing mode is inline-RTL, our "start" and
6915 // "end" will be reversed from this physical-coord view, so we have to swap
6917 if (aIter
.mTableWM
.IsBidiRTL()) {
6918 std::swap(result
.mStartBevelSide
, result
.mEndBevelSide
);
6919 std::swap(result
.mStartBevelOffset
, result
.mEndBevelOffset
);
6922 return Some(result
);
6926 * Paint the inline-dir segment
6927 * @param aIter - iterator containing the structural information
6928 * @param aDrawTarget - the draw target
6930 void BCInlineDirSeg::Paint(BCPaintBorderIterator
& aIter
,
6931 DrawTarget
& aDrawTarget
) {
6932 Maybe
<BCBorderParameters
> param
= BuildBorderParameters(aIter
);
6933 if (param
.isNothing()) {
6937 nsCSSRendering::DrawTableBorderSegment(
6938 aDrawTarget
, param
->mBorderStyle
, param
->mBorderColor
, param
->mBorderRect
,
6939 param
->mAppUnitsPerDevPixel
, param
->mStartBevelSide
,
6940 param
->mStartBevelOffset
, param
->mEndBevelSide
, param
->mEndBevelOffset
);
6943 void BCInlineDirSeg::CreateWebRenderCommands(
6944 BCPaintBorderIterator
& aIter
, wr::DisplayListBuilder
& aBuilder
,
6945 const layers::StackingContextHelper
& aSc
, const nsPoint
& aPt
) {
6946 Maybe
<BCBorderParameters
> param
= BuildBorderParameters(aIter
);
6947 if (param
.isNothing()) {
6951 CreateWRCommandsForBorderSegment(*param
, aBuilder
, aSc
, aPt
);
6955 * Advance the start point of a segment
6957 void BCInlineDirSeg::AdvanceOffsetI() { mOffsetI
+= (mLength
- mEndOffset
); }
6960 * Accumulate the current segment
6962 void BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator
& aIter
) {
6963 mLength
+= aIter
.mBlockDirInfo
[aIter
.GetRelativeColIndex()].mColWidth
;
6967 * store the column width information while painting inline-dir segment
6969 void BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex
) {
6970 if (IsTableIEndMost()) {
6971 mBlockDirInfo
[aIndex
].mColWidth
= mBlockDirInfo
[aIndex
- 1].mColWidth
;
6973 nsTableColFrame
* col
= mTableFirstInFlow
->GetColFrame(mColIndex
);
6975 mBlockDirInfo
[aIndex
].mColWidth
= col
->ISize(mTableWM
);
6979 * Determine if a block-dir segment owns the corner
6981 bool BCPaintBorderIterator::BlockDirSegmentOwnsCorner() {
6982 LogicalSide cornerOwnerSide
= LogicalSide::BStart
;
6985 mBCData
->GetCorner(cornerOwnerSide
, bevel
);
6987 // unitialized ownerside, bevel
6988 return (LogicalSide::BStart
== cornerOwnerSide
) ||
6989 (LogicalSide::BEnd
== cornerOwnerSide
);
6993 * Paint if necessary an inline-dir segment, otherwise accumulate it
6994 * @param aDrawTarget - the draw target
6996 void BCPaintBorderIterator::AccumulateOrDoActionInlineDirSegment(
6997 BCPaintBorderAction
& aAction
) {
6998 int32_t relColIndex
= GetRelativeColIndex();
6999 // store the current col width if it hasn't been already
7000 if (mBlockDirInfo
[relColIndex
].mColWidth
< 0) {
7001 StoreColumnWidth(relColIndex
);
7004 BCBorderOwner borderOwner
= eCellOwner
;
7005 BCBorderOwner ignoreBorderOwner
;
7006 bool isSegStart
= true;
7007 bool ignoreSegStart
;
7009 nscoord iStartSegISize
=
7010 mBCData
? mBCData
->GetIStartEdge(ignoreBorderOwner
, ignoreSegStart
) : 0;
7011 nscoord bStartSegBSize
=
7012 mBCData
? mBCData
->GetBStartEdge(borderOwner
, isSegStart
) : 0;
7014 if (mIsNewRow
|| (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) {
7015 // reset for every new row and on the bottom of the last row
7016 mInlineSeg
.mOffsetB
= mNextOffsetB
;
7017 mNextOffsetB
= mNextOffsetB
+ mRow
->BSize(mTableWM
);
7018 mInlineSeg
.mOffsetI
= mInitialOffsetI
;
7019 mInlineSeg
.Start(*this, borderOwner
, iStartSegISize
, bStartSegBSize
);
7022 if (!IsDamageAreaIStartMost() &&
7023 (isSegStart
|| IsDamageAreaIEndMost() || BlockDirSegmentOwnsCorner())) {
7024 // paint the previous seg or the current one if IsDamageAreaIEndMost()
7025 if (mInlineSeg
.mLength
> 0) {
7026 mInlineSeg
.GetIEndCorner(*this, iStartSegISize
);
7027 if (mInlineSeg
.mWidth
> 0) {
7028 if (aAction
.mMode
== BCPaintBorderAction::Mode::Paint
) {
7029 mInlineSeg
.Paint(*this, aAction
.mPaintData
.mDrawTarget
);
7031 MOZ_ASSERT(aAction
.mMode
==
7032 BCPaintBorderAction::Mode::CreateWebRenderCommands
);
7033 mInlineSeg
.CreateWebRenderCommands(
7034 *this, aAction
.mCreateWebRenderCommandsData
.mBuilder
,
7035 aAction
.mCreateWebRenderCommandsData
.mSc
,
7036 aAction
.mCreateWebRenderCommandsData
.mOffsetToReferenceFrame
);
7039 mInlineSeg
.AdvanceOffsetI();
7041 mInlineSeg
.Start(*this, borderOwner
, iStartSegISize
, bStartSegBSize
);
7043 mInlineSeg
.IncludeCurrentBorder(*this);
7044 mBlockDirInfo
[relColIndex
].mWidth
= iStartSegISize
;
7045 mBlockDirInfo
[relColIndex
].mLastCell
= mCell
;
7049 * Paint if necessary a block-dir segment, otherwise accumulate it
7050 * @param aDrawTarget - the draw target
7052 void BCPaintBorderIterator::AccumulateOrDoActionBlockDirSegment(
7053 BCPaintBorderAction
& aAction
) {
7054 BCBorderOwner borderOwner
= eCellOwner
;
7055 BCBorderOwner ignoreBorderOwner
;
7056 bool isSegStart
= true;
7057 bool ignoreSegStart
;
7059 nscoord blockSegISize
=
7060 mBCData
? mBCData
->GetIStartEdge(borderOwner
, isSegStart
) : 0;
7061 nscoord inlineSegBSize
=
7062 mBCData
? mBCData
->GetBStartEdge(ignoreBorderOwner
, ignoreSegStart
) : 0;
7064 int32_t relColIndex
= GetRelativeColIndex();
7065 BCBlockDirSeg
& blockDirSeg
= mBlockDirInfo
[relColIndex
];
7066 if (!blockDirSeg
.mCol
) { // on the first damaged row and the first segment in
7068 blockDirSeg
.Initialize(*this);
7069 blockDirSeg
.Start(*this, borderOwner
, blockSegISize
, inlineSegBSize
,
7073 if (!IsDamageAreaBStartMost() &&
7074 (isSegStart
|| IsDamageAreaBEndMost() || IsAfterRepeatedHeader() ||
7075 StartRepeatedFooter())) {
7076 Maybe
<nscoord
> emptyRowEndSize
;
7077 // paint the previous seg or the current one if IsDamageAreaBEndMost()
7078 if (blockDirSeg
.mLength
> 0) {
7079 blockDirSeg
.GetBEndCorner(*this, inlineSegBSize
);
7080 if (blockDirSeg
.mWidth
> 0) {
7081 if (aAction
.mMode
== BCPaintBorderAction::Mode::Paint
) {
7082 blockDirSeg
.Paint(*this, aAction
.mPaintData
.mDrawTarget
,
7085 MOZ_ASSERT(aAction
.mMode
==
7086 BCPaintBorderAction::Mode::CreateWebRenderCommands
);
7087 blockDirSeg
.CreateWebRenderCommands(
7088 *this, inlineSegBSize
,
7089 aAction
.mCreateWebRenderCommandsData
.mBuilder
,
7090 aAction
.mCreateWebRenderCommandsData
.mSc
,
7091 aAction
.mCreateWebRenderCommandsData
.mOffsetToReferenceFrame
);
7094 blockDirSeg
.AdvanceOffsetB();
7095 if (mRow
->PrincipalChildList().IsEmpty()) {
7096 emptyRowEndSize
= Some(mRow
->BSize(mTableWM
));
7099 blockDirSeg
.Start(*this, borderOwner
, blockSegISize
, inlineSegBSize
,
7102 blockDirSeg
.IncludeCurrentBorder(*this);
7103 mPrevInlineSegBSize
= inlineSegBSize
;
7107 * Reset the block-dir information cache
7109 void BCPaintBorderIterator::ResetVerInfo() {
7110 if (mBlockDirInfo
) {
7111 memset(mBlockDirInfo
.get(), 0,
7112 mDamageArea
.ColCount() * sizeof(BCBlockDirSeg
));
7113 // XXX reinitialize properly
7114 for (auto xIndex
: IntegerRange(mDamageArea
.ColCount())) {
7115 mBlockDirInfo
[xIndex
].mColWidth
= -1;
7120 void nsTableFrame::IterateBCBorders(BCPaintBorderAction
& aAction
,
7121 const nsRect
& aDirtyRect
) {
7122 // We first transfer the aDirtyRect into cellmap coordinates to compute which
7123 // cell borders need to be painted
7124 BCPaintBorderIterator
iter(this);
7125 if (!iter
.SetDamageArea(aDirtyRect
)) return;
7127 // XXX comment still has physical terminology
7128 // First, paint all of the vertical borders from top to bottom and left to
7129 // right as they become complete. They are painted first, since they are less
7130 // efficient to paint than horizontal segments. They were stored with as few
7131 // segments as possible (since horizontal borders are painted last and
7132 // possibly over them). For every cell in a row that fails in the damage are
7133 // we look up if the current border would start a new segment, if so we paint
7134 // the previously stored vertical segment and start a new segment. After
7135 // this we the now active segment with the current border. These
7136 // segments are stored in mBlockDirInfo to be used on the next row
7137 for (iter
.First(); !iter
.mAtEnd
; iter
.Next()) {
7138 iter
.AccumulateOrDoActionBlockDirSegment(aAction
);
7141 // Next, paint all of the inline-dir border segments from bStart to bEnd reuse
7142 // the mBlockDirInfo array to keep track of col widths and block-dir segments
7143 // for corner calculations
7145 for (iter
.First(); !iter
.mAtEnd
; iter
.Next()) {
7146 iter
.AccumulateOrDoActionInlineDirSegment(aAction
);
7151 * Method to paint BCBorders, this does not use currently display lists although
7152 * it will do this in future
7153 * @param aDrawTarget - the rendering context
7154 * @param aDirtyRect - inside this rectangle the BC Borders will redrawn
7156 void nsTableFrame::PaintBCBorders(DrawTarget
& aDrawTarget
,
7157 const nsRect
& aDirtyRect
) {
7158 BCPaintBorderAction
action(aDrawTarget
);
7159 IterateBCBorders(action
, aDirtyRect
);
7162 void nsTableFrame::CreateWebRenderCommandsForBCBorders(
7163 wr::DisplayListBuilder
& aBuilder
,
7164 const mozilla::layers::StackingContextHelper
& aSc
,
7165 const nsRect
& aVisibleRect
, const nsPoint
& aOffsetToReferenceFrame
) {
7166 BCPaintBorderAction
action(aBuilder
, aSc
, aOffsetToReferenceFrame
);
7167 // We always draw whole table border for webrender. Passing the visible rect
7169 IterateBCBorders(action
, aVisibleRect
- aOffsetToReferenceFrame
);
7172 bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex
, int32_t aNumEffCols
) {
7173 bool result
= false;
7174 nsTableCellMap
* cellMap
= GetCellMap();
7175 MOZ_ASSERT(cellMap
, "bad call, cellMap not yet allocated.");
7177 result
= cellMap
->RowHasSpanningCells(aRowIndex
, aNumEffCols
);
7182 bool nsTableFrame::RowIsSpannedInto(int32_t aRowIndex
, int32_t aNumEffCols
) {
7183 bool result
= false;
7184 nsTableCellMap
* cellMap
= GetCellMap();
7185 MOZ_ASSERT(cellMap
, "bad call, cellMap not yet allocated.");
7187 result
= cellMap
->RowIsSpannedInto(aRowIndex
, aNumEffCols
);
7193 void nsTableFrame::InvalidateTableFrame(nsIFrame
* aFrame
,
7194 const nsRect
& aOrigRect
,
7195 const nsRect
& aOrigInkOverflow
,
7196 bool aIsFirstReflow
) {
7197 nsIFrame
* parent
= aFrame
->GetParent();
7198 NS_ASSERTION(parent
, "What happened here?");
7200 if (parent
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
7201 // Don't bother; we'll invalidate the parent's overflow rect when
7202 // we finish reflowing it.
7206 // The part that looks at both the rect and the overflow rect is a
7207 // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent
7208 // description of its hackishness.
7210 // This doesn't really make sense now that we have DLBI.
7211 // This code can probably be simplified a fair bit.
7212 nsRect inkOverflow
= aFrame
->InkOverflowRect();
7213 if (aIsFirstReflow
|| aOrigRect
.TopLeft() != aFrame
->GetPosition() ||
7214 aOrigInkOverflow
.TopLeft() != inkOverflow
.TopLeft()) {
7215 // Invalidate the old and new overflow rects. Note that if the
7216 // frame moved, we can't just use aOrigInkOverflow, since it's in
7217 // coordinates relative to the old position. So invalidate via
7218 // aFrame's parent, and reposition that overflow rect to the right
7220 // XXXbz this doesn't handle outlines, does it?
7221 aFrame
->InvalidateFrame();
7222 parent
->InvalidateFrameWithRect(aOrigInkOverflow
+ aOrigRect
.TopLeft());
7223 } else if (aOrigRect
.Size() != aFrame
->GetSize() ||
7224 aOrigInkOverflow
.Size() != inkOverflow
.Size()) {
7225 aFrame
->InvalidateFrameWithRect(aOrigInkOverflow
);
7226 aFrame
->InvalidateFrame();
7230 void nsTableFrame::AppendDirectlyOwnedAnonBoxes(
7231 nsTArray
<OwnedAnonBox
>& aResult
) {
7232 nsIFrame
* wrapper
= GetParent();
7233 MOZ_ASSERT(wrapper
->Style()->GetPseudoType() == PseudoStyleType::tableWrapper
,
7234 "What happened to our parent?");
7235 aResult
.AppendElement(
7236 OwnedAnonBox(wrapper
, &UpdateStyleOfOwnedAnonBoxesForTableWrapper
));
7240 void nsTableFrame::UpdateStyleOfOwnedAnonBoxesForTableWrapper(
7241 nsIFrame
* aOwningFrame
, nsIFrame
* aWrapperFrame
,
7242 ServoRestyleState
& aRestyleState
) {
7244 aWrapperFrame
->Style()->GetPseudoType() == PseudoStyleType::tableWrapper
,
7245 "What happened to our parent?");
7247 RefPtr
<ComputedStyle
> newStyle
=
7248 aRestyleState
.StyleSet().ResolveInheritingAnonymousBoxStyle(
7249 PseudoStyleType::tableWrapper
, aOwningFrame
->Style());
7251 // Figure out whether we have an actual change. It's important that we do
7252 // this, even though all the wrapper's changes are due to properties it
7253 // inherits from us, because it's possible that no one ever asked us for those
7254 // style structs and hence changes to them aren't reflected in
7255 // the handled changes at all.
7257 // Also note that extensions can add/remove stylesheets that change the styles
7258 // of anonymous boxes directly, so we need to handle that potential change
7261 // NOTE(emilio): We can't use the ChangesHandledFor optimization (and we
7262 // assert against that), because the table wrapper is up in the frame tree
7263 // compared to the owner frame.
7264 uint32_t equalStructs
; // Not used, actually.
7265 nsChangeHint wrapperHint
=
7266 aWrapperFrame
->Style()->CalcStyleDifference(*newStyle
, &equalStructs
);
7269 aRestyleState
.ChangeList().AppendChange(
7270 aWrapperFrame
, aWrapperFrame
->GetContent(), wrapperHint
);
7273 for (nsIFrame
* cur
= aWrapperFrame
; cur
; cur
= cur
->GetNextContinuation()) {
7274 cur
->SetComputedStyle(newStyle
);
7277 MOZ_ASSERT(!aWrapperFrame
->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES
),
7278 "Wrapper frame doesn't have any anon boxes of its own!");
7283 nsRect
nsDisplayTableItem::GetBounds(nsDisplayListBuilder
* aBuilder
,
7284 bool* aSnap
) const {
7286 return mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
7289 nsDisplayTableBackgroundSet::nsDisplayTableBackgroundSet(
7290 nsDisplayListBuilder
* aBuilder
, nsIFrame
* aTable
)
7291 : mBuilder(aBuilder
),
7292 mColGroupBackgrounds(aBuilder
),
7293 mColBackgrounds(aBuilder
),
7294 mCurrentScrollParentId(aBuilder
->GetCurrentScrollParentId()) {
7295 mPrevTableBackgroundSet
= mBuilder
->SetTableBackgroundSet(this);
7296 mozilla::DebugOnly
<const nsIFrame
*> reference
=
7297 mBuilder
->FindReferenceFrameFor(aTable
, &mToReferenceFrame
);
7298 MOZ_ASSERT(nsLayoutUtils::FindNearestCommonAncestorFrame(reference
, aTable
));
7299 mDirtyRect
= mBuilder
->GetDirtyRect();
7300 mCombinedTableClipChain
=
7301 mBuilder
->ClipState().GetCurrentCombinedClipChain(aBuilder
);
7302 mTableASR
= mBuilder
->CurrentActiveScrolledRoot();
7305 // A display item that draws all collapsed borders for a table.
7306 // At some point, we may want to find a nicer partitioning for dividing
7307 // border-collapse segments into their own display items.
7308 class nsDisplayTableBorderCollapse final
: public nsDisplayTableItem
{
7310 nsDisplayTableBorderCollapse(nsDisplayListBuilder
* aBuilder
,
7311 nsTableFrame
* aFrame
)
7312 : nsDisplayTableItem(aBuilder
, aFrame
) {
7313 MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse
);
7315 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableBorderCollapse
)
7317 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
7318 bool CreateWebRenderCommands(
7319 wr::DisplayListBuilder
& aBuilder
, wr::IpcResourceUpdateQueue
& aResources
,
7320 const StackingContextHelper
& aSc
,
7321 layers::RenderRootStateManager
* aManager
,
7322 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
7323 NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE
)
7326 void nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder
* aBuilder
,
7328 nsPoint pt
= ToReferenceFrame();
7329 DrawTarget
* drawTarget
= aCtx
->GetDrawTarget();
7331 gfxPoint devPixelOffset
= nsLayoutUtils::PointToGfxPoint(
7332 pt
, mFrame
->PresContext()->AppUnitsPerDevPixel());
7334 // XXX we should probably get rid of this translation at some stage
7335 // But that would mean modifying PaintBCBorders, ugh
7336 AutoRestoreTransform
autoRestoreTransform(drawTarget
);
7337 drawTarget
->SetTransform(
7338 drawTarget
->GetTransform().PreTranslate(ToPoint(devPixelOffset
)));
7340 static_cast<nsTableFrame
*>(mFrame
)->PaintBCBorders(
7341 *drawTarget
, GetPaintRect(aBuilder
, aCtx
) - pt
);
7344 bool nsDisplayTableBorderCollapse::CreateWebRenderCommands(
7345 wr::DisplayListBuilder
& aBuilder
, wr::IpcResourceUpdateQueue
& aResources
,
7346 const StackingContextHelper
& aSc
,
7347 mozilla::layers::RenderRootStateManager
* aManager
,
7348 nsDisplayListBuilder
* aDisplayListBuilder
) {
7350 static_cast<nsTableFrame
*>(mFrame
)->CreateWebRenderCommandsForBCBorders(
7351 aBuilder
, aSc
, GetBounds(aDisplayListBuilder
, &dummy
),
7352 ToReferenceFrame());
7356 } // namespace mozilla