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