Backed out changeset c20fe6f0a048 (bug 1825384) for causing table related reftest...
[gecko.git] / layout / tables / nsTableFrame.cpp
bloba475a4eac0b67f2506367eb29f7f935387eb1bc9
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTableFrame.h"
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/gfx/Helpers.h"
11 #include "mozilla/Likely.h"
12 #include "mozilla/MathAlgorithms.h"
13 #include "mozilla/IntegerRange.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/PresShellInlines.h"
16 #include "mozilla/WritingModes.h"
18 #include "gfxContext.h"
19 #include "nsCOMPtr.h"
20 #include "mozilla/ComputedStyle.h"
21 #include "nsIFrameInlines.h"
22 #include "nsFrameList.h"
23 #include "nsStyleConsts.h"
24 #include "nsIContent.h"
25 #include "nsCellMap.h"
26 #include "nsTableCellFrame.h"
27 #include "nsHTMLParts.h"
28 #include "nsTableColFrame.h"
29 #include "nsTableColGroupFrame.h"
30 #include "nsTableRowFrame.h"
31 #include "nsTableRowGroupFrame.h"
32 #include "nsTableWrapperFrame.h"
34 #include "BasicTableLayoutStrategy.h"
35 #include "FixedTableLayoutStrategy.h"
37 #include "nsPresContext.h"
38 #include "nsContentUtils.h"
39 #include "nsCSSRendering.h"
40 #include "nsGkAtoms.h"
41 #include "nsCSSAnonBoxes.h"
42 #include "nsIScriptError.h"
43 #include "nsFrameManager.h"
44 #include "nsError.h"
45 #include "nsCSSFrameConstructor.h"
46 #include "mozilla/Range.h"
47 #include "mozilla/RestyleManager.h"
48 #include "mozilla/ServoStyleSet.h"
49 #include "nsDisplayList.h"
50 #include "nsIScrollableFrame.h"
51 #include "nsCSSProps.h"
52 #include "nsLayoutUtils.h"
53 #include "nsStyleChangeList.h"
54 #include <algorithm>
56 #include "mozilla/layers/StackingContextHelper.h"
57 #include "mozilla/layers/RenderRootStateManager.h"
59 using namespace mozilla;
60 using namespace mozilla::image;
61 using namespace mozilla::layout;
63 using mozilla::gfx::AutoRestoreTransform;
64 using mozilla::gfx::DrawTarget;
65 using mozilla::gfx::Float;
66 using mozilla::gfx::ToDeviceColor;
68 /********************************************************************************
69 ** TableReflowInput **
70 ********************************************************************************/
72 namespace mozilla {
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
82 nscoord mICoord;
84 // Running block-offset
85 nscoord mBCoord;
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");
92 nsTableFrame* table =
93 static_cast<nsTableFrame*>(mReflowInput.mFrame->FirstInFlow());
94 WritingMode wm = aReflowInput.GetWritingMode();
95 LogicalMargin borderPadding = table->GetChildAreaOffset(wm, &mReflowInput);
97 mICoord = borderPadding.IStart(wm) + table->GetColSpacing(-1);
98 mBCoord = borderPadding.BStart(wm); // cellspacing added during reflow
100 // XXX do we actually need to check for unconstrained inline-size here?
101 if (NS_UNCONSTRAINEDSIZE != mAvailSize.ISize(wm)) {
102 int32_t colCount = table->GetColCount();
103 mAvailSize.ISize(wm) -= borderPadding.IStartEnd(wm) +
104 table->GetColSpacing(-1) +
105 table->GetColSpacing(colCount);
106 mAvailSize.ISize(wm) = std::max(0, mAvailSize.ISize(wm));
109 if (NS_UNCONSTRAINEDSIZE != mAvailSize.BSize(wm)) {
110 mAvailSize.BSize(wm) -= borderPadding.BStartEnd(wm) +
111 table->GetRowSpacing(-1) +
112 table->GetRowSpacing(table->GetRowCount());
113 mAvailSize.BSize(wm) = std::max(0, mAvailSize.BSize(wm));
117 void ReduceAvailableBSizeBy(WritingMode aWM, nscoord aAmount) {
118 if (mAvailSize.BSize(aWM) == NS_UNCONSTRAINEDSIZE) {
119 return;
121 mAvailSize.BSize(aWM) -= aAmount;
122 mAvailSize.BSize(aWM) = std::max(0, mAvailSize.BSize(aWM));
126 } // namespace mozilla
128 /********************************************************************************
129 ** nsTableFrame **
130 ********************************************************************************/
132 struct BCPropertyData {
133 BCPropertyData()
134 : mBStartBorderWidth(0),
135 mIEndBorderWidth(0),
136 mBEndBorderWidth(0),
137 mIStartBorderWidth(0),
138 mIStartCellBorderWidth(0),
139 mIEndCellBorderWidth(0) {}
140 TableArea mDamageArea;
141 BCPixelSize mBStartBorderWidth;
142 BCPixelSize mIEndBorderWidth;
143 BCPixelSize mBEndBorderWidth;
144 BCPixelSize mIStartBorderWidth;
145 BCPixelSize mIStartCellBorderWidth;
146 BCPixelSize mIEndCellBorderWidth;
149 ComputedStyle* nsTableFrame::GetParentComputedStyle(
150 nsIFrame** aProviderFrame) const {
151 // Since our parent, the table wrapper frame, returned this frame, we
152 // must return whatever our parent would normally have returned.
154 MOZ_ASSERT(GetParent(), "table constructed without table wrapper");
155 if (!mContent->GetParent() && !Style()->IsPseudoOrAnonBox()) {
156 // We're the root. We have no ComputedStyle parent.
157 *aProviderFrame = nullptr;
158 return nullptr;
161 return GetParent()->DoGetParentComputedStyle(aProviderFrame);
164 nsTableFrame::nsTableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
165 ClassID aID)
166 : nsContainerFrame(aStyle, aPresContext, aID) {
167 memset(&mBits, 0, sizeof(mBits));
170 void nsTableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
171 nsIFrame* aPrevInFlow) {
172 MOZ_ASSERT(!mCellMap, "Init called twice");
173 MOZ_ASSERT(!mTableLayoutStrategy, "Init called twice");
174 MOZ_ASSERT(!aPrevInFlow || aPrevInFlow->IsTableFrame(),
175 "prev-in-flow must be of same type");
177 // Let the base class do its processing
178 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
180 // see if border collapse is on, if so set it
181 const nsStyleTableBorder* tableStyle = StyleTableBorder();
182 bool borderCollapse =
183 (StyleBorderCollapse::Collapse == tableStyle->mBorderCollapse);
184 SetBorderCollapse(borderCollapse);
185 if (borderCollapse) {
186 SetNeedToCalcHasBCBorders(true);
189 if (!aPrevInFlow) {
190 // If we're the first-in-flow, we manage the cell map & layout strategy that
191 // get used by our continuation chain:
192 mCellMap = MakeUnique<nsTableCellMap>(*this, borderCollapse);
193 if (IsAutoLayout()) {
194 mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this);
195 } else {
196 mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this);
198 } else {
199 // Set my isize, because all frames in a table flow are the same isize and
200 // code in nsTableWrapperFrame depends on this being set.
201 WritingMode wm = GetWritingMode();
202 SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm)));
206 // Define here (Rather than in the header), even if it's trival, to avoid
207 // UniquePtr members causing compile errors when their destructors are
208 // implicitly inserted into this destructor. Destruction requires
209 // the full definition of types that these UniquePtrs are managing, and
210 // the header only has forward declarations of them.
211 nsTableFrame::~nsTableFrame() = default;
213 void nsTableFrame::Destroy(DestroyContext& aContext) {
214 MOZ_ASSERT(!mBits.mIsDestroying);
215 mBits.mIsDestroying = true;
216 mColGroups.DestroyFrames(aContext);
217 nsContainerFrame::Destroy(aContext);
220 // Make sure any views are positioned properly
221 void nsTableFrame::RePositionViews(nsIFrame* aFrame) {
222 nsContainerFrame::PositionFrameView(aFrame);
223 nsContainerFrame::PositionChildViews(aFrame);
226 static bool IsRepeatedFrame(nsIFrame* kidFrame) {
227 return (kidFrame->IsTableRowFrame() || kidFrame->IsTableRowGroupFrame()) &&
228 kidFrame->HasAnyStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
231 bool nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame,
232 nsIFrame* aNextFrame) {
233 const nsStyleDisplay* display = aSourceFrame->StyleDisplay();
234 nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame);
235 // don't allow a page break after a repeated element ...
236 if ((display->BreakAfter() || (prevRg && prevRg->HasInternalBreakAfter())) &&
237 !IsRepeatedFrame(aSourceFrame)) {
238 return !(aNextFrame && IsRepeatedFrame(aNextFrame)); // or before
241 if (aNextFrame) {
242 display = aNextFrame->StyleDisplay();
243 // don't allow a page break before a repeated element ...
244 nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame);
245 if ((display->BreakBefore() ||
246 (nextRg && nextRg->HasInternalBreakBefore())) &&
247 !IsRepeatedFrame(aNextFrame)) {
248 return !IsRepeatedFrame(aSourceFrame); // or after
251 return false;
254 /* static */
255 void nsTableFrame::PositionedTablePartMaybeChanged(nsIFrame* aFrame,
256 ComputedStyle* aOldStyle) {
257 const bool wasPositioned =
258 aOldStyle && aOldStyle->IsAbsPosContainingBlock(aFrame);
259 const bool isPositioned = aFrame->IsAbsPosContainingBlock();
260 MOZ_ASSERT(isPositioned == aFrame->Style()->IsAbsPosContainingBlock(aFrame));
261 if (wasPositioned == isPositioned) {
262 return;
265 nsTableFrame* tableFrame = GetTableFrame(aFrame);
266 MOZ_ASSERT(tableFrame, "Should have a table frame here");
267 tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
269 // Retrieve the positioned parts array for this table.
270 FrameTArray* positionedParts =
271 tableFrame->GetProperty(PositionedTablePartArray());
273 // Lazily create the array if it doesn't exist yet.
274 if (!positionedParts) {
275 positionedParts = new FrameTArray;
276 tableFrame->SetProperty(PositionedTablePartArray(), positionedParts);
279 if (isPositioned) {
280 // Add this frame to the list.
281 positionedParts->AppendElement(aFrame);
282 } else {
283 positionedParts->RemoveElement(aFrame);
287 /* static */
288 void nsTableFrame::MaybeUnregisterPositionedTablePart(nsIFrame* aFrame) {
289 if (!aFrame->IsAbsPosContainingBlock()) {
290 return;
292 nsTableFrame* tableFrame = GetTableFrame(aFrame);
293 tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
295 if (tableFrame->IsDestroying()) {
296 return; // We're throwing the table away anyways.
299 // Retrieve the positioned parts array for this table.
300 FrameTArray* positionedParts =
301 tableFrame->GetProperty(PositionedTablePartArray());
303 // Remove the frame.
304 MOZ_ASSERT(
305 positionedParts && positionedParts->Contains(aFrame),
306 "Asked to unregister a positioned table part that wasn't registered");
307 if (positionedParts) {
308 positionedParts->RemoveElement(aFrame);
312 // XXX this needs to be cleaned up so that the frame constructor breaks out col
313 // group frames into a separate child list, bug 343048.
314 void nsTableFrame::SetInitialChildList(ChildListID aListID,
315 nsFrameList&& aChildList) {
316 if (aListID != FrameChildListID::Principal) {
317 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
318 return;
321 MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(),
322 "unexpected second call to SetInitialChildList");
323 #ifdef DEBUG
324 for (nsIFrame* f : aChildList) {
325 MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
327 #endif
329 // XXXbz the below code is an icky cesspit that's only needed in its current
330 // form for two reasons:
331 // 1) Both rowgroups and column groups come in on the principal child list.
332 while (aChildList.NotEmpty()) {
333 nsIFrame* childFrame = aChildList.FirstChild();
334 aChildList.RemoveFirstChild();
335 const nsStyleDisplay* childDisplay = childFrame->StyleDisplay();
337 if (mozilla::StyleDisplay::TableColumnGroup == childDisplay->mDisplay) {
338 NS_ASSERTION(childFrame->IsTableColGroupFrame(),
339 "This is not a colgroup");
340 mColGroups.AppendFrame(nullptr, childFrame);
341 } else { // row groups and unknown frames go on the main list for now
342 mFrames.AppendFrame(nullptr, childFrame);
346 // If we have a prev-in-flow, then we're a table that has been split and
347 // so don't treat this like an append
348 if (!GetPrevInFlow()) {
349 // process col groups first so that real cols get constructed before
350 // anonymous ones due to cells in rows.
351 InsertColGroups(0, mColGroups);
352 InsertRowGroups(mFrames);
353 // calc collapsing borders
354 if (IsBorderCollapse()) {
355 SetFullBCDamageArea();
360 void nsTableFrame::RowOrColSpanChanged(nsTableCellFrame* aCellFrame) {
361 if (aCellFrame) {
362 nsTableCellMap* cellMap = GetCellMap();
363 if (cellMap) {
364 // for now just remove the cell from the map and reinsert it
365 uint32_t rowIndex = aCellFrame->RowIndex();
366 uint32_t colIndex = aCellFrame->ColIndex();
367 RemoveCell(aCellFrame, rowIndex);
368 AutoTArray<nsTableCellFrame*, 1> cells;
369 cells.AppendElement(aCellFrame);
370 InsertCells(cells, rowIndex, colIndex - 1);
372 // XXX Should this use IntrinsicDirty::FrameAncestorsAndDescendants? It
373 // currently doesn't need to, but it might given more optimization.
374 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
375 NS_FRAME_IS_DIRTY);
380 /* ****** CellMap methods ******* */
382 /* return the effective col count */
383 int32_t nsTableFrame::GetEffectiveColCount() const {
384 int32_t colCount = GetColCount();
385 if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) {
386 nsTableCellMap* cellMap = GetCellMap();
387 if (!cellMap) {
388 return 0;
390 // don't count cols at the end that don't have originating cells
391 for (int32_t colIdx = colCount - 1; colIdx >= 0; colIdx--) {
392 if (cellMap->GetNumCellsOriginatingInCol(colIdx) > 0) {
393 break;
395 colCount--;
398 return colCount;
401 int32_t nsTableFrame::GetIndexOfLastRealCol() {
402 int32_t numCols = mColFrames.Length();
403 if (numCols > 0) {
404 for (int32_t colIdx = numCols - 1; colIdx >= 0; colIdx--) {
405 nsTableColFrame* colFrame = GetColFrame(colIdx);
406 if (colFrame) {
407 if (eColAnonymousCell != colFrame->GetColType()) {
408 return colIdx;
413 return -1;
416 nsTableColFrame* nsTableFrame::GetColFrame(int32_t aColIndex) const {
417 MOZ_ASSERT(!GetPrevInFlow(), "GetColFrame called on next in flow");
418 int32_t numCols = mColFrames.Length();
419 if ((aColIndex >= 0) && (aColIndex < numCols)) {
420 MOZ_ASSERT(mColFrames.ElementAt(aColIndex));
421 return mColFrames.ElementAt(aColIndex);
422 } else {
423 MOZ_ASSERT_UNREACHABLE("invalid col index");
424 return nullptr;
428 int32_t nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex,
429 const nsTableCellFrame& aCell) const {
430 nsTableCellMap* cellMap = GetCellMap();
431 MOZ_ASSERT(nullptr != cellMap, "bad call, cellMap not yet allocated.");
433 return cellMap->GetEffectiveRowSpan(aRowIndex, aCell.ColIndex());
436 int32_t nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell,
437 nsCellMap* aCellMap) {
438 nsTableCellMap* tableCellMap = GetCellMap();
439 if (!tableCellMap) ABORT1(1);
441 uint32_t colIndex = aCell.ColIndex();
442 uint32_t rowIndex = aCell.RowIndex();
444 if (aCellMap)
445 return aCellMap->GetRowSpan(rowIndex, colIndex, true);
446 else
447 return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex);
450 int32_t nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell,
451 nsCellMap* aCellMap) const {
452 nsTableCellMap* tableCellMap = GetCellMap();
453 if (!tableCellMap) ABORT1(1);
455 uint32_t colIndex = aCell.ColIndex();
456 uint32_t rowIndex = aCell.RowIndex();
458 if (aCellMap)
459 return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex);
460 else
461 return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex);
464 bool nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const {
465 nsTableCellMap* tableCellMap = GetCellMap();
466 if (!tableCellMap) ABORT1(1);
467 return tableCellMap->HasMoreThanOneCell(aRowIndex);
470 void nsTableFrame::AdjustRowIndices(int32_t aRowIndex, int32_t aAdjustment) {
471 // Iterate over the row groups and adjust the row indices of all rows
472 // whose index is >= aRowIndex.
473 RowGroupArray rowGroups;
474 OrderRowGroups(rowGroups);
476 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
477 rowGroups[rgIdx]->AdjustRowIndices(aRowIndex, aAdjustment);
481 void nsTableFrame::ResetRowIndices(
482 const nsFrameList::Slice& aRowGroupsToExclude) {
483 // Iterate over the row groups and adjust the row indices of all rows
484 // omit the rowgroups that will be inserted later
485 mDeletedRowIndexRanges.clear();
487 RowGroupArray rowGroups;
488 OrderRowGroups(rowGroups);
490 nsTHashSet<nsTableRowGroupFrame*> excludeRowGroups;
491 for (nsIFrame* excludeRowGroup : aRowGroupsToExclude) {
492 excludeRowGroups.Insert(
493 static_cast<nsTableRowGroupFrame*>(excludeRowGroup));
494 #ifdef DEBUG
496 // Check to make sure that the row indices of all rows in excluded row
497 // groups are '0' (i.e. the initial value since they haven't been added
498 // yet)
499 const nsFrameList& rowFrames = excludeRowGroup->PrincipalChildList();
500 for (nsIFrame* r : rowFrames) {
501 auto* row = static_cast<nsTableRowFrame*>(r);
502 MOZ_ASSERT(row->GetRowIndex() == 0,
503 "exclusions cannot be used for rows that were already added,"
504 "because we'd need to process mDeletedRowIndexRanges");
507 #endif
510 int32_t rowIndex = 0;
511 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
512 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
513 if (!excludeRowGroups.Contains(rgFrame)) {
514 const nsFrameList& rowFrames = rgFrame->PrincipalChildList();
515 for (nsIFrame* r : rowFrames) {
516 if (mozilla::StyleDisplay::TableRow == r->StyleDisplay()->mDisplay) {
517 auto* row = static_cast<nsTableRowFrame*>(r);
518 row->SetRowIndex(rowIndex);
519 rowIndex++;
526 void nsTableFrame::InsertColGroups(int32_t aStartColIndex,
527 const nsFrameList::Slice& aColGroups) {
528 int32_t colIndex = aStartColIndex;
530 // XXX: We cannot use range-based for loop because AddColsToTable() can
531 // destroy the nsTableColGroupFrame in the slice we're traversing! Need to
532 // check the validity of *colGroupIter.
533 auto colGroupIter = aColGroups.begin();
534 for (auto colGroupIterEnd = aColGroups.end();
535 *colGroupIter && colGroupIter != colGroupIterEnd; ++colGroupIter) {
536 MOZ_ASSERT((*colGroupIter)->IsTableColGroupFrame());
537 auto* cgFrame = static_cast<nsTableColGroupFrame*>(*colGroupIter);
538 cgFrame->SetStartColumnIndex(colIndex);
539 cgFrame->AddColsToTable(colIndex, false, cgFrame->PrincipalChildList());
540 int32_t numCols = cgFrame->GetColCount();
541 colIndex += numCols;
544 if (*colGroupIter) {
545 nsTableColGroupFrame::ResetColIndices(*colGroupIter, colIndex);
549 void nsTableFrame::InsertCol(nsTableColFrame& aColFrame, int32_t aColIndex) {
550 mColFrames.InsertElementAt(aColIndex, &aColFrame);
551 nsTableColType insertedColType = aColFrame.GetColType();
552 int32_t numCacheCols = mColFrames.Length();
553 nsTableCellMap* cellMap = GetCellMap();
554 if (cellMap) {
555 int32_t numMapCols = cellMap->GetColCount();
556 if (numCacheCols > numMapCols) {
557 bool removedFromCache = false;
558 if (eColAnonymousCell != insertedColType) {
559 nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1);
560 if (lastCol) {
561 nsTableColType lastColType = lastCol->GetColType();
562 if (eColAnonymousCell == lastColType) {
563 // remove the col from the cache
564 mColFrames.RemoveLastElement();
565 // remove the col from the synthetic col group
566 nsTableColGroupFrame* lastColGroup =
567 (nsTableColGroupFrame*)mColGroups.LastChild();
568 if (lastColGroup) {
569 MOZ_ASSERT(lastColGroup->IsSynthetic());
570 DestroyContext context(PresShell());
571 lastColGroup->RemoveChild(context, *lastCol, false);
573 // remove the col group if it is empty
574 if (lastColGroup->GetColCount() <= 0) {
575 mColGroups.DestroyFrame(context, (nsIFrame*)lastColGroup);
578 removedFromCache = true;
582 if (!removedFromCache) {
583 cellMap->AddColsAtEnd(1);
587 // for now, just bail and recalc all of the collapsing borders
588 if (IsBorderCollapse()) {
589 TableArea damageArea(aColIndex, 0, GetColCount() - aColIndex,
590 GetRowCount());
591 AddBCDamageArea(damageArea);
595 void nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame,
596 int32_t aColIndex, bool aRemoveFromCache,
597 bool aRemoveFromCellMap) {
598 if (aRemoveFromCache) {
599 mColFrames.RemoveElementAt(aColIndex);
601 if (aRemoveFromCellMap) {
602 nsTableCellMap* cellMap = GetCellMap();
603 if (cellMap) {
604 // If we have some anonymous cols at the end already, we just
605 // add a new anonymous col.
606 if (!mColFrames.IsEmpty() &&
607 mColFrames.LastElement() && // XXXbz is this ever null?
608 mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
609 AppendAnonymousColFrames(1);
610 } else {
611 // All of our colframes correspond to actual <col> tags. It's possible
612 // that we still have at least as many <col> tags as we have logical
613 // columns from cells, but we might have one less. Handle the latter
614 // case as follows: First ask the cellmap to drop its last col if it
615 // doesn't have any actual cells in it. Then call
616 // MatchCellMapToColCache to append an anonymous column if it's needed;
617 // this needs to be after RemoveColsAtEnd, since it will determine the
618 // need for a new column frame based on the width of the cell map.
619 cellMap->RemoveColsAtEnd();
620 MatchCellMapToColCache(cellMap);
624 // for now, just bail and recalc all of the collapsing borders
625 if (IsBorderCollapse()) {
626 TableArea damageArea(0, 0, GetColCount(), GetRowCount());
627 AddBCDamageArea(damageArea);
631 /** Get the cell map for this table frame. It is not always mCellMap.
632 * Only the first-in-flow has a legit cell map.
634 nsTableCellMap* nsTableFrame::GetCellMap() const {
635 return static_cast<nsTableFrame*>(FirstInFlow())->mCellMap.get();
638 nsTableColGroupFrame* nsTableFrame::CreateSyntheticColGroupFrame() {
639 nsIContent* colGroupContent = GetContent();
640 nsPresContext* presContext = PresContext();
641 mozilla::PresShell* presShell = presContext->PresShell();
643 RefPtr<ComputedStyle> colGroupStyle;
644 colGroupStyle = presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
645 PseudoStyleType::tableColGroup);
646 // Create a col group frame
647 nsTableColGroupFrame* newFrame =
648 NS_NewTableColGroupFrame(presShell, colGroupStyle);
649 newFrame->SetIsSynthetic();
650 newFrame->Init(colGroupContent, this, nullptr);
651 return newFrame;
654 void nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) {
655 MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");
656 // get the last col group frame
657 nsTableColGroupFrame* colGroupFrame =
658 static_cast<nsTableColGroupFrame*>(mColGroups.LastChild());
660 if (!colGroupFrame || !colGroupFrame->IsSynthetic()) {
661 int32_t colIndex = (colGroupFrame) ? colGroupFrame->GetStartColumnIndex() +
662 colGroupFrame->GetColCount()
663 : 0;
664 colGroupFrame = CreateSyntheticColGroupFrame();
665 if (!colGroupFrame) {
666 return;
668 // add the new frame to the child list
669 mColGroups.AppendFrame(this, colGroupFrame);
670 colGroupFrame->SetStartColumnIndex(colIndex);
672 AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell,
673 true);
676 // XXX this needs to be moved to nsCSSFrameConstructor
677 // Right now it only creates the col frames at the end
678 void nsTableFrame::AppendAnonymousColFrames(
679 nsTableColGroupFrame* aColGroupFrame, int32_t aNumColsToAdd,
680 nsTableColType aColType, bool aAddToTable) {
681 MOZ_ASSERT(aColGroupFrame, "null frame");
682 MOZ_ASSERT(aColType != eColAnonymousCol, "Shouldn't happen");
683 MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_.");
685 mozilla::PresShell* presShell = PresShell();
687 // Get the last col frame
688 nsFrameList newColFrames;
690 int32_t startIndex = mColFrames.Length();
691 int32_t lastIndex = startIndex + aNumColsToAdd - 1;
693 for (int32_t childX = startIndex; childX <= lastIndex; childX++) {
694 // all anonymous cols that we create here use a pseudo ComputedStyle of the
695 // col group
696 nsIContent* iContent = aColGroupFrame->GetContent();
697 RefPtr<ComputedStyle> computedStyle =
698 presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
699 PseudoStyleType::tableCol);
700 // ASSERTION to check for bug 54454 sneaking back in...
701 NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames");
703 // create the new col frame
704 nsIFrame* colFrame = NS_NewTableColFrame(presShell, computedStyle);
705 ((nsTableColFrame*)colFrame)->SetColType(aColType);
706 colFrame->Init(iContent, aColGroupFrame, nullptr);
708 newColFrames.AppendFrame(nullptr, colFrame);
710 nsFrameList& cols = aColGroupFrame->GetWritableChildList();
711 nsIFrame* oldLastCol = cols.LastChild();
712 const nsFrameList::Slice& newCols =
713 cols.InsertFrames(nullptr, oldLastCol, std::move(newColFrames));
714 if (aAddToTable) {
715 // get the starting col index in the cache
716 int32_t startColIndex;
717 if (oldLastCol) {
718 startColIndex =
719 static_cast<nsTableColFrame*>(oldLastCol)->GetColIndex() + 1;
720 } else {
721 startColIndex = aColGroupFrame->GetStartColumnIndex();
724 aColGroupFrame->AddColsToTable(startColIndex, true, newCols);
728 void nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) {
729 int32_t numColsInMap = GetColCount();
730 int32_t numColsInCache = mColFrames.Length();
731 int32_t numColsToAdd = numColsInMap - numColsInCache;
732 if (numColsToAdd > 0) {
733 // this sets the child list, updates the col cache and cell map
734 AppendAnonymousColFrames(numColsToAdd);
736 if (numColsToAdd < 0) {
737 int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd);
738 // if the cell map has fewer cols than the cache, correct it
739 if (numColsNotRemoved > 0) {
740 aCellMap->AddColsAtEnd(numColsNotRemoved);
745 void nsTableFrame::DidResizeColumns() {
746 MOZ_ASSERT(!GetPrevInFlow(), "should only be called on first-in-flow");
748 if (mBits.mResizedColumns) return; // already marked
750 for (nsTableFrame* f = this; f;
751 f = static_cast<nsTableFrame*>(f->GetNextInFlow()))
752 f->mBits.mResizedColumns = true;
755 void nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, int32_t aRowIndex) {
756 nsTableCellMap* cellMap = GetCellMap();
757 if (cellMap) {
758 TableArea damageArea(0, 0, 0, 0);
759 cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea);
760 MatchCellMapToColCache(cellMap);
761 if (IsBorderCollapse()) {
762 AddBCDamageArea(damageArea);
767 void nsTableFrame::InsertCells(nsTArray<nsTableCellFrame*>& aCellFrames,
768 int32_t aRowIndex, int32_t aColIndexBefore) {
769 nsTableCellMap* cellMap = GetCellMap();
770 if (cellMap) {
771 TableArea damageArea(0, 0, 0, 0);
772 cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore, damageArea);
773 MatchCellMapToColCache(cellMap);
774 if (IsBorderCollapse()) {
775 AddBCDamageArea(damageArea);
780 // this removes the frames from the col group and table, but not the cell map
781 int32_t nsTableFrame::DestroyAnonymousColFrames(int32_t aNumFrames) {
782 // only remove cols that are of type eTypeAnonymous cell (they are at the end)
783 int32_t endIndex = mColFrames.Length() - 1;
784 int32_t startIndex = (endIndex - aNumFrames) + 1;
785 int32_t numColsRemoved = 0;
786 DestroyContext context(PresShell());
787 for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) {
788 nsTableColFrame* colFrame = GetColFrame(colIdx);
789 if (colFrame && (eColAnonymousCell == colFrame->GetColType())) {
790 auto* cgFrame = static_cast<nsTableColGroupFrame*>(colFrame->GetParent());
791 // remove the frame from the colgroup
792 cgFrame->RemoveChild(context, *colFrame, false);
793 // remove the frame from the cache, but not the cell map
794 RemoveCol(nullptr, colIdx, true, false);
795 numColsRemoved++;
796 } else {
797 break;
800 return (aNumFrames - numColsRemoved);
803 void nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, int32_t aRowIndex) {
804 nsTableCellMap* cellMap = GetCellMap();
805 if (cellMap) {
806 TableArea damageArea(0, 0, 0, 0);
807 cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea);
808 MatchCellMapToColCache(cellMap);
809 if (IsBorderCollapse()) {
810 AddBCDamageArea(damageArea);
815 int32_t nsTableFrame::GetStartRowIndex(
816 const nsTableRowGroupFrame* aRowGroupFrame) const {
817 RowGroupArray orderedRowGroups;
818 OrderRowGroups(orderedRowGroups);
820 int32_t rowIndex = 0;
821 for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
822 nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
823 if (rgFrame == aRowGroupFrame) {
824 break;
826 int32_t numRows = rgFrame->GetRowCount();
827 rowIndex += numRows;
829 return rowIndex;
832 // this cannot extend beyond a single row group
833 void nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame,
834 int32_t aRowIndex,
835 nsTArray<nsTableRowFrame*>& aRowFrames) {
836 nsTableCellMap* cellMap = GetCellMap();
837 if (cellMap) {
838 int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex;
839 InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true);
843 // this cannot extend beyond a single row group
844 int32_t nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame,
845 nsTArray<nsTableRowFrame*>& aRowFrames,
846 int32_t aRowIndex, bool aConsiderSpans) {
847 #ifdef DEBUG_TABLE_CELLMAP
848 printf("=== insertRowsBefore firstRow=%d \n", aRowIndex);
849 Dump(true, false, true);
850 #endif
852 int32_t numColsToAdd = 0;
853 nsTableCellMap* cellMap = GetCellMap();
854 if (cellMap) {
855 TableArea damageArea(0, 0, 0, 0);
856 bool shouldRecalculateIndex = !IsDeletedRowIndexRangesEmpty();
857 if (shouldRecalculateIndex) {
858 ResetRowIndices(nsFrameList::Slice(nullptr, nullptr));
860 int32_t origNumRows = cellMap->GetRowCount();
861 int32_t numNewRows = aRowFrames.Length();
862 cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans,
863 damageArea);
864 MatchCellMapToColCache(cellMap);
866 // Perform row index adjustment only if row indices were not
867 // reset above
868 if (!shouldRecalculateIndex) {
869 if (aRowIndex < origNumRows) {
870 AdjustRowIndices(aRowIndex, numNewRows);
873 // assign the correct row indices to the new rows. If they were
874 // recalculated above it may not have been done correctly because each row
875 // is constructed with index 0
876 for (int32_t rowB = 0; rowB < numNewRows; rowB++) {
877 nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowB);
878 rowFrame->SetRowIndex(aRowIndex + rowB);
882 if (IsBorderCollapse()) {
883 AddBCDamageArea(damageArea);
886 #ifdef DEBUG_TABLE_CELLMAP
887 printf("=== insertRowsAfter \n");
888 Dump(true, false, true);
889 #endif
891 return numColsToAdd;
894 void nsTableFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) {
895 if (mDeletedRowIndexRanges.empty()) {
896 mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
897 aDeletedRowStoredIndex, aDeletedRowStoredIndex));
898 return;
901 // Find the position of the current deleted row's stored index
902 // among the previous deleted row index ranges and merge ranges if
903 // they are consecutive, else add a new (disjoint) range to the map.
904 // Call to mDeletedRowIndexRanges.upper_bound is
905 // O(log(mDeletedRowIndexRanges.size())) therefore call to
906 // AddDeletedRowIndex is also ~O(log(mDeletedRowIndexRanges.size()))
908 // greaterIter = will point to smallest range in the map with lower value
909 // greater than the aDeletedRowStoredIndex.
910 // If no such value exists, point to end of map.
911 // smallerIter = will point to largest range in the map with higher value
912 // smaller than the aDeletedRowStoredIndex
913 // If no such value exists, point to beginning of map.
914 // i.e. when both values exist below is true:
915 // smallerIter->second < aDeletedRowStoredIndex < greaterIter->first
916 auto greaterIter = mDeletedRowIndexRanges.upper_bound(aDeletedRowStoredIndex);
917 auto smallerIter = greaterIter;
919 if (smallerIter != mDeletedRowIndexRanges.begin()) {
920 smallerIter--;
921 // While greaterIter might be out-of-bounds (by being equal to end()),
922 // smallerIter now cannot be, since we returned early above for a 0-size
923 // map.
926 // Note: smallerIter can only be equal to greaterIter when both
927 // of them point to the beginning of the map and in that case smallerIter
928 // does not "exist" but we clip smallerIter to point to beginning of map
929 // so that it doesn't point to something unknown or outside the map boundry.
930 // Note: When greaterIter is not the end (i.e. it "exists") upper_bound()
931 // ensures aDeletedRowStoredIndex < greaterIter->first so no need to
932 // assert that.
933 MOZ_ASSERT(smallerIter == greaterIter ||
934 aDeletedRowStoredIndex > smallerIter->second,
935 "aDeletedRowIndexRanges already contains aDeletedRowStoredIndex! "
936 "Trying to delete an already deleted row?");
938 if (smallerIter->second == aDeletedRowStoredIndex - 1) {
939 if (greaterIter != mDeletedRowIndexRanges.end() &&
940 greaterIter->first == aDeletedRowStoredIndex + 1) {
941 // merge current index with smaller and greater range as they are
942 // consecutive
943 smallerIter->second = greaterIter->second;
944 mDeletedRowIndexRanges.erase(greaterIter);
945 } else {
946 // add aDeletedRowStoredIndex in the smaller range as it is consecutive
947 smallerIter->second = aDeletedRowStoredIndex;
949 } else if (greaterIter != mDeletedRowIndexRanges.end() &&
950 greaterIter->first == aDeletedRowStoredIndex + 1) {
951 // add aDeletedRowStoredIndex in the greater range as it is consecutive
952 mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
953 aDeletedRowStoredIndex, greaterIter->second));
954 mDeletedRowIndexRanges.erase(greaterIter);
955 } else {
956 // add new range as aDeletedRowStoredIndex is disjoint from existing ranges
957 mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
958 aDeletedRowStoredIndex, aDeletedRowStoredIndex));
962 int32_t nsTableFrame::GetAdjustmentForStoredIndex(int32_t aStoredIndex) {
963 if (mDeletedRowIndexRanges.empty()) return 0;
965 int32_t adjustment = 0;
967 // O(log(mDeletedRowIndexRanges.size()))
968 auto endIter = mDeletedRowIndexRanges.upper_bound(aStoredIndex);
969 for (auto iter = mDeletedRowIndexRanges.begin(); iter != endIter; ++iter) {
970 adjustment += iter->second - iter->first + 1;
973 return adjustment;
976 // this cannot extend beyond a single row group
977 void nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame,
978 int32_t aNumRowsToRemove, bool aConsiderSpans) {
979 #ifdef TBD_OPTIMIZATION
980 // decide if we need to rebalance. we have to do this here because the row
981 // group cannot do it when it gets the dirty reflow corresponding to the frame
982 // being destroyed
983 bool stopTelling = false;
984 for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking);
985 kidFrame = kidFrame->GetNextSibling()) {
986 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
987 if (cellFrame) {
988 stopTelling = tableFrame->CellChangedWidth(
989 *cellFrame, cellFrame->GetPass1MaxElementWidth(),
990 cellFrame->GetMaximumWidth(), true);
993 // XXX need to consider what happens if there are cells that have rowspans
994 // into the deleted row. Need to consider moving rows if a rebalance doesn't
995 // happen
996 #endif
998 int32_t firstRowIndex = aFirstRowFrame.GetRowIndex();
999 #ifdef DEBUG_TABLE_CELLMAP
1000 printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex,
1001 aNumRowsToRemove);
1002 Dump(true, false, true);
1003 #endif
1004 nsTableCellMap* cellMap = GetCellMap();
1005 if (cellMap) {
1006 TableArea damageArea(0, 0, 0, 0);
1008 // Mark rows starting from aFirstRowFrame to the next 'aNumRowsToRemove-1'
1009 // number of rows as deleted.
1010 nsTableRowGroupFrame* parentFrame = aFirstRowFrame.GetTableRowGroupFrame();
1011 parentFrame->MarkRowsAsDeleted(aFirstRowFrame, aNumRowsToRemove);
1013 cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans,
1014 damageArea);
1015 MatchCellMapToColCache(cellMap);
1016 if (IsBorderCollapse()) {
1017 AddBCDamageArea(damageArea);
1021 #ifdef DEBUG_TABLE_CELLMAP
1022 printf("=== removeRowsAfter\n");
1023 Dump(true, true, true);
1024 #endif
1027 // collect the rows ancestors of aFrame
1028 int32_t nsTableFrame::CollectRows(nsIFrame* aFrame,
1029 nsTArray<nsTableRowFrame*>& aCollection) {
1030 MOZ_ASSERT(aFrame, "null frame");
1031 int32_t numRows = 0;
1032 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
1033 aCollection.AppendElement(static_cast<nsTableRowFrame*>(childFrame));
1034 numRows++;
1036 return numRows;
1039 void nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) {
1040 #ifdef DEBUG_TABLE_CELLMAP
1041 printf("=== insertRowGroupsBefore\n");
1042 Dump(true, false, true);
1043 #endif
1044 nsTableCellMap* cellMap = GetCellMap();
1045 if (cellMap) {
1046 RowGroupArray orderedRowGroups;
1047 OrderRowGroups(orderedRowGroups);
1049 AutoTArray<nsTableRowFrame*, 8> rows;
1050 // Loop over the rowgroups and check if some of them are new, if they are
1051 // insert cellmaps in the order that is predefined by OrderRowGroups,
1052 // XXXbz this code is O(N*M) where N is number of new rowgroups
1053 // and M is number of rowgroups we have!
1054 uint32_t rgIndex;
1055 for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
1056 for (nsIFrame* rowGroup : aRowGroups) {
1057 if (orderedRowGroups[rgIndex] == rowGroup) {
1058 nsTableRowGroupFrame* priorRG =
1059 (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
1060 // create and add the cell map for the row group
1061 cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG);
1063 break;
1067 cellMap->Synchronize(this);
1068 ResetRowIndices(aRowGroups);
1070 // now that the cellmaps are reordered too insert the rows
1071 for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
1072 for (nsIFrame* rowGroup : aRowGroups) {
1073 if (orderedRowGroups[rgIndex] == rowGroup) {
1074 nsTableRowGroupFrame* priorRG =
1075 (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
1076 // collect the new row frames in an array and add them to the table
1077 int32_t numRows = CollectRows(rowGroup, rows);
1078 if (numRows > 0) {
1079 int32_t rowIndex = 0;
1080 if (priorRG) {
1081 int32_t priorNumRows = priorRG->GetRowCount();
1082 rowIndex = priorRG->GetStartRowIndex() + priorNumRows;
1084 InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true);
1085 rows.Clear();
1087 break;
1092 #ifdef DEBUG_TABLE_CELLMAP
1093 printf("=== insertRowGroupsAfter\n");
1094 Dump(true, true, true);
1095 #endif
1098 /////////////////////////////////////////////////////////////////////////////
1099 // Child frame enumeration
1101 const nsFrameList& nsTableFrame::GetChildList(ChildListID aListID) const {
1102 if (aListID == FrameChildListID::ColGroup) {
1103 return mColGroups;
1105 return nsContainerFrame::GetChildList(aListID);
1108 void nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
1109 nsContainerFrame::GetChildLists(aLists);
1110 mColGroups.AppendIfNonempty(aLists, FrameChildListID::ColGroup);
1113 static inline bool FrameHasBorder(nsIFrame* f) {
1114 if (!f->StyleVisibility()->IsVisible()) {
1115 return false;
1118 return f->StyleBorder()->HasBorder();
1121 void nsTableFrame::CalcHasBCBorders() {
1122 if (!IsBorderCollapse()) {
1123 SetHasBCBorders(false);
1124 return;
1127 if (FrameHasBorder(this)) {
1128 SetHasBCBorders(true);
1129 return;
1132 // Check col and col group has borders.
1133 for (nsIFrame* f : this->GetChildList(FrameChildListID::ColGroup)) {
1134 if (FrameHasBorder(f)) {
1135 SetHasBCBorders(true);
1136 return;
1139 nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(f);
1140 for (nsTableColFrame* col = colGroup->GetFirstColumn(); col;
1141 col = col->GetNextCol()) {
1142 if (FrameHasBorder(col)) {
1143 SetHasBCBorders(true);
1144 return;
1149 // check row group, row and cell has borders.
1150 RowGroupArray rowGroups;
1151 OrderRowGroups(rowGroups);
1152 for (nsTableRowGroupFrame* rowGroup : rowGroups) {
1153 if (FrameHasBorder(rowGroup)) {
1154 SetHasBCBorders(true);
1155 return;
1158 for (nsTableRowFrame* row = rowGroup->GetFirstRow(); row;
1159 row = row->GetNextRow()) {
1160 if (FrameHasBorder(row)) {
1161 SetHasBCBorders(true);
1162 return;
1165 for (nsTableCellFrame* cell = row->GetFirstCell(); cell;
1166 cell = cell->GetNextCell()) {
1167 if (FrameHasBorder(cell)) {
1168 SetHasBCBorders(true);
1169 return;
1175 SetHasBCBorders(false);
1178 namespace mozilla {
1179 class nsDisplayTableBorderCollapse;
1182 // table paint code is concerned primarily with borders and bg color
1183 // SEC: TODO: adjust the rect for captions
1184 void nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1185 const nsDisplayListSet& aLists) {
1186 DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255, 128, 255));
1188 DisplayBorderBackgroundOutline(aBuilder, aLists);
1190 nsDisplayTableBackgroundSet tableBGs(aBuilder, this);
1191 nsDisplayListCollection lists(aBuilder);
1193 // This is similar to what
1194 // nsContainerFrame::BuildDisplayListForNonBlockChildren does, except that we
1195 // allow the children's background and borders to go in our BorderBackground
1196 // list. This doesn't really affect background painting --- the children won't
1197 // actually draw their own backgrounds because the nsTableFrame already drew
1198 // them, unless a child has its own stacking context, in which case the child
1199 // won't use its passed-in BorderBackground list anyway. It does affect cell
1200 // borders though; this lets us get cell borders into the nsTableFrame's
1201 // BorderBackground list.
1202 for (nsIFrame* colGroup :
1203 FirstContinuation()->GetChildList(FrameChildListID::ColGroup)) {
1204 for (nsIFrame* col : colGroup->PrincipalChildList()) {
1205 tableBGs.AddColumn((nsTableColFrame*)col);
1209 for (nsIFrame* kid : PrincipalChildList()) {
1210 BuildDisplayListForChild(aBuilder, kid, lists);
1213 tableBGs.MoveTo(aLists);
1214 lists.MoveTo(aLists);
1216 if (IsVisibleForPainting()) {
1217 // In the collapsed border model, overlay all collapsed borders.
1218 if (IsBorderCollapse()) {
1219 if (HasBCBorders()) {
1220 aLists.BorderBackground()->AppendNewToTop<nsDisplayTableBorderCollapse>(
1221 aBuilder, this);
1223 } else {
1224 const nsStyleBorder* borderStyle = StyleBorder();
1225 if (borderStyle->HasBorder()) {
1226 aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder,
1227 this);
1233 nsMargin nsTableFrame::GetDeflationForBackground(
1234 nsPresContext* aPresContext) const {
1235 if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() ||
1236 !IsBorderCollapse())
1237 return nsMargin(0, 0, 0, 0);
1239 WritingMode wm = GetWritingMode();
1240 return GetOuterBCBorder(wm).GetPhysicalMargin(wm);
1243 LogicalSides nsTableFrame::GetLogicalSkipSides() const {
1244 LogicalSides skip(mWritingMode);
1245 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
1246 StyleBoxDecorationBreak::Clone)) {
1247 return skip;
1250 // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto
1251 // account for pagination
1252 if (GetPrevInFlow()) {
1253 skip |= eLogicalSideBitsBStart;
1255 if (GetNextInFlow()) {
1256 skip |= eLogicalSideBitsBEnd;
1258 return skip;
1261 void nsTableFrame::SetColumnDimensions(nscoord aBSize, WritingMode aWM,
1262 const LogicalMargin& aBorderPadding,
1263 const nsSize& aContainerSize) {
1264 const nscoord colBSize =
1265 aBSize - (aBorderPadding.BStartEnd(aWM) + GetRowSpacing(-1) +
1266 GetRowSpacing(GetRowCount()));
1267 int32_t colIdx = 0;
1268 LogicalPoint colGroupOrigin(aWM,
1269 aBorderPadding.IStart(aWM) + GetColSpacing(-1),
1270 aBorderPadding.BStart(aWM) + GetRowSpacing(-1));
1271 nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
1272 for (nsIFrame* colGroupFrame : mColGroups) {
1273 MOZ_ASSERT(colGroupFrame->IsTableColGroupFrame());
1274 // first we need to figure out the size of the colgroup
1275 int32_t groupFirstCol = colIdx;
1276 nscoord colGroupISize = 0;
1277 nscoord cellSpacingI = 0;
1278 const nsFrameList& columnList = colGroupFrame->PrincipalChildList();
1279 for (nsIFrame* colFrame : columnList) {
1280 if (mozilla::StyleDisplay::TableColumn ==
1281 colFrame->StyleDisplay()->mDisplay) {
1282 NS_ASSERTION(colIdx < GetColCount(), "invalid number of columns");
1283 cellSpacingI = GetColSpacing(colIdx);
1284 colGroupISize +=
1285 fif->GetColumnISizeFromFirstInFlow(colIdx) + cellSpacingI;
1286 ++colIdx;
1289 if (colGroupISize) {
1290 colGroupISize -= cellSpacingI;
1293 LogicalRect colGroupRect(aWM, colGroupOrigin.I(aWM), colGroupOrigin.B(aWM),
1294 colGroupISize, colBSize);
1295 colGroupFrame->SetRect(aWM, colGroupRect, aContainerSize);
1296 nsSize colGroupSize = colGroupFrame->GetSize();
1298 // then we can place the columns correctly within the group
1299 colIdx = groupFirstCol;
1300 LogicalPoint colOrigin(aWM);
1301 for (nsIFrame* colFrame : columnList) {
1302 if (mozilla::StyleDisplay::TableColumn ==
1303 colFrame->StyleDisplay()->mDisplay) {
1304 nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
1305 LogicalRect colRect(aWM, colOrigin.I(aWM), colOrigin.B(aWM), colISize,
1306 colBSize);
1307 colFrame->SetRect(aWM, colRect, colGroupSize);
1308 cellSpacingI = GetColSpacing(colIdx);
1309 colOrigin.I(aWM) += colISize + cellSpacingI;
1310 ++colIdx;
1314 colGroupOrigin.I(aWM) += colGroupISize + cellSpacingI;
1318 // SEC: TODO need to worry about continuing frames prev/next in flow for
1319 // splitting across pages.
1321 // XXX this could be made more general to handle row modifications that change
1322 // the table bsize, but first we need to scrutinize every Invalidate
1323 void nsTableFrame::ProcessRowInserted(nscoord aNewBSize) {
1324 SetRowInserted(false); // reset the bit that got us here
1325 nsTableFrame::RowGroupArray rowGroups;
1326 OrderRowGroups(rowGroups);
1327 // find the row group containing the inserted row
1328 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
1329 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
1330 NS_ASSERTION(rgFrame, "Must have rgFrame here");
1331 // find the row that was inserted first
1332 for (nsIFrame* childFrame : rgFrame->PrincipalChildList()) {
1333 nsTableRowFrame* rowFrame = do_QueryFrame(childFrame);
1334 if (rowFrame) {
1335 if (rowFrame->IsFirstInserted()) {
1336 rowFrame->SetFirstInserted(false);
1337 // damage the table from the 1st row inserted to the end of the table
1338 nsIFrame::InvalidateFrame();
1339 // XXXbz didn't we do this up front? Why do we need to do it again?
1340 SetRowInserted(false);
1341 return; // found it, so leave
1348 /* virtual */
1349 void nsTableFrame::MarkIntrinsicISizesDirty() {
1350 nsITableLayoutStrategy* tls = LayoutStrategy();
1351 if (MOZ_UNLIKELY(!tls)) {
1352 // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame()
1353 // walking up the ancestor chain in a table next-in-flow. In this case
1354 // our original first-in-flow (which owns the TableLayoutStrategy) has
1355 // already been destroyed and unhooked from the flow chain and thusly
1356 // LayoutStrategy() returns null. All the frames in the flow will be
1357 // destroyed so no need to mark anything dirty here. See bug 595758.
1358 return;
1360 tls->MarkIntrinsicISizesDirty();
1362 // XXXldb Call SetBCDamageArea?
1364 nsContainerFrame::MarkIntrinsicISizesDirty();
1367 /* virtual */
1368 nscoord nsTableFrame::GetMinISize(gfxContext* aRenderingContext) {
1369 if (NeedToCalcBCBorders()) CalcBCBorders();
1371 ReflowColGroups(aRenderingContext);
1373 return LayoutStrategy()->GetMinISize(aRenderingContext);
1376 /* virtual */
1377 nscoord nsTableFrame::GetPrefISize(gfxContext* aRenderingContext) {
1378 if (NeedToCalcBCBorders()) CalcBCBorders();
1380 ReflowColGroups(aRenderingContext);
1382 return LayoutStrategy()->GetPrefISize(aRenderingContext, false);
1385 /* virtual */ nsIFrame::IntrinsicSizeOffsetData
1386 nsTableFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) {
1387 IntrinsicSizeOffsetData result =
1388 nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis);
1390 result.margin = 0;
1392 if (IsBorderCollapse()) {
1393 result.padding = 0;
1395 WritingMode wm = GetWritingMode();
1396 LogicalMargin outerBC = GetIncludedOuterBCBorder(wm);
1397 result.border = outerBC.IStartEnd(wm);
1400 return result;
1403 /* virtual */
1404 nsIFrame::SizeComputationResult nsTableFrame::ComputeSize(
1405 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
1406 nscoord aAvailableISize, const LogicalSize& aMargin,
1407 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
1408 ComputeSizeFlags aFlags) {
1409 // Only table wrapper calls this method, and it should use our writing mode.
1410 MOZ_ASSERT(aWM == GetWritingMode(),
1411 "aWM should be the same as our writing mode!");
1413 auto result = nsContainerFrame::ComputeSize(
1414 aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding,
1415 aSizeOverrides, aFlags);
1417 // If our containing block wants to override inner table frame's inline-size
1418 // (e.g. when resolving flex base size), don't enforce the min inline-size
1419 // later in this method.
1420 if (aSizeOverrides.mApplyOverridesVerbatim && aSizeOverrides.mStyleISize &&
1421 aSizeOverrides.mStyleISize->IsLengthPercentage()) {
1422 return result;
1425 // If we're a container for font size inflation, then shrink
1426 // wrapping inside of us should not apply font size inflation.
1427 AutoMaybeDisableFontInflation an(this);
1429 // Tables never shrink below their min inline-size.
1430 nscoord minISize = GetMinISize(aRenderingContext);
1431 if (minISize > result.mLogicalSize.ISize(aWM)) {
1432 result.mLogicalSize.ISize(aWM) = minISize;
1435 return result;
1438 nscoord nsTableFrame::TableShrinkISizeToFit(gfxContext* aRenderingContext,
1439 nscoord aISizeInCB) {
1440 // If we're a container for font size inflation, then shrink
1441 // wrapping inside of us should not apply font size inflation.
1442 AutoMaybeDisableFontInflation an(this);
1444 nscoord result;
1445 nscoord minISize = GetMinISize(aRenderingContext);
1446 if (minISize > aISizeInCB) {
1447 result = minISize;
1448 } else {
1449 // Tables shrink inline-size to fit with a slightly different algorithm
1450 // from the one they use for their intrinsic isize (the difference
1451 // relates to handling of percentage isizes on columns). So this
1452 // function differs from nsIFrame::ShrinkISizeToFit by only the
1453 // following line.
1454 // Since we've already called GetMinISize, we don't need to do any
1455 // of the other stuff GetPrefISize does.
1456 nscoord prefISize = LayoutStrategy()->GetPrefISize(aRenderingContext, true);
1457 if (prefISize > aISizeInCB) {
1458 result = aISizeInCB;
1459 } else {
1460 result = prefISize;
1463 return result;
1466 /* virtual */
1467 LogicalSize nsTableFrame::ComputeAutoSize(
1468 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
1469 nscoord aAvailableISize, const LogicalSize& aMargin,
1470 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
1471 ComputeSizeFlags aFlags) {
1472 // Tables always shrink-wrap.
1473 nscoord cbBased =
1474 aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM);
1475 return LogicalSize(aWM, TableShrinkISizeToFit(aRenderingContext, cbBased),
1476 NS_UNCONSTRAINEDSIZE);
1479 // Return true if aParentReflowInput.frame or any of its ancestors within
1480 // the containing table have non-auto bsize. (e.g. pct or fixed bsize)
1481 bool nsTableFrame::AncestorsHaveStyleBSize(
1482 const ReflowInput& aParentReflowInput) {
1483 WritingMode wm = aParentReflowInput.GetWritingMode();
1484 for (const ReflowInput* rs = &aParentReflowInput; rs && rs->mFrame;
1485 rs = rs->mParentReflowInput) {
1486 LayoutFrameType frameType = rs->mFrame->Type();
1487 if (LayoutFrameType::TableCell == frameType ||
1488 LayoutFrameType::TableRow == frameType ||
1489 LayoutFrameType::TableRowGroup == frameType) {
1490 const auto& bsize = rs->mStylePosition->BSize(wm);
1491 // calc() with both lengths and percentages treated like 'auto' on
1492 // internal table elements
1493 if (!bsize.IsAuto() && !bsize.HasLengthAndPercentage()) {
1494 return true;
1496 } else if (LayoutFrameType::Table == frameType) {
1497 // we reached the containing table, so always return
1498 return !rs->mStylePosition->BSize(wm).IsAuto();
1501 return false;
1504 // See if a special block-size reflow needs to occur and if so,
1505 // call RequestSpecialBSizeReflow
1506 void nsTableFrame::CheckRequestSpecialBSizeReflow(
1507 const ReflowInput& aReflowInput) {
1508 NS_ASSERTION(aReflowInput.mFrame->IsTableCellFrame() ||
1509 aReflowInput.mFrame->IsTableRowFrame() ||
1510 aReflowInput.mFrame->IsTableRowGroupFrame() ||
1511 aReflowInput.mFrame->IsTableFrame(),
1512 "unexpected frame type");
1513 WritingMode wm = aReflowInput.GetWritingMode();
1514 if (!aReflowInput.mFrame->GetPrevInFlow() && // 1st in flow
1515 (NS_UNCONSTRAINEDSIZE ==
1516 aReflowInput.ComputedBSize() || // no computed bsize
1517 0 == aReflowInput.ComputedBSize()) &&
1518 aReflowInput.mStylePosition->BSize(wm)
1519 .ConvertsToPercentage() && // pct bsize
1520 nsTableFrame::AncestorsHaveStyleBSize(*aReflowInput.mParentReflowInput)) {
1521 nsTableFrame::RequestSpecialBSizeReflow(aReflowInput);
1525 // Notify the frame and its ancestors (up to the containing table) that a
1526 // special bsize reflow will occur. During a special bsize reflow, a table, row
1527 // group, row, or cell returns the last size it was reflowed at. However, the
1528 // table may change the bsize of row groups, rows, cells in
1529 // DistributeBSizeToRows after. And the row group can change the bsize of rows,
1530 // cells in CalculateRowBSizes.
1531 void nsTableFrame::RequestSpecialBSizeReflow(const ReflowInput& aReflowInput) {
1532 // notify the frame and its ancestors of the special reflow, stopping at the
1533 // containing table
1534 for (const ReflowInput* rs = &aReflowInput; rs && rs->mFrame;
1535 rs = rs->mParentReflowInput) {
1536 LayoutFrameType frameType = rs->mFrame->Type();
1537 NS_ASSERTION(LayoutFrameType::TableCell == frameType ||
1538 LayoutFrameType::TableRow == frameType ||
1539 LayoutFrameType::TableRowGroup == frameType ||
1540 LayoutFrameType::Table == frameType,
1541 "unexpected frame type");
1543 rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
1544 if (LayoutFrameType::Table == frameType) {
1545 NS_ASSERTION(rs != &aReflowInput,
1546 "should not request special bsize reflow for table");
1547 // always stop when we reach a table
1548 break;
1553 /******************************************************************************************
1554 * Before reflow, intrinsic inline-size calculation is done using GetMinISize
1555 * and GetPrefISize. This used to be known as pass 1 reflow.
1557 * After the intrinsic isize calculation, the table determines the
1558 * column widths using BalanceColumnISizes() and
1559 * then reflows each child again with a constrained avail isize. This reflow is
1560 * referred to as the pass 2 reflow.
1562 * A special bsize reflow (pass 3 reflow) can occur during an initial or resize
1563 * reflow if (a) a row group, row, cell, or a frame inside a cell has a percent
1564 * bsize but no computed bsize or (b) in paginated mode, a table has a bsize.
1565 * (a) supports percent nested tables contained inside cells whose bsizes aren't
1566 * known until after the pass 2 reflow. (b) is necessary because the table
1567 * cannot split until after the pass 2 reflow. The mechanics of the special
1568 * bsize reflow (variety a) are as follows:
1570 * 1) Each table related frame (table, row group, row, cell) implements
1571 * NeedsSpecialReflow() to indicate that it should get the reflow. It does
1572 * this when it has a percent bsize but no computed bsize by calling
1573 * CheckRequestSpecialBSizeReflow(). This method calls
1574 * RequestSpecialBSizeReflow() which calls SetNeedSpecialReflow() on its
1575 * ancestors until it reaches the containing table and calls
1576 * SetNeedToInitiateSpecialReflow() on it. For percent bsize frames inside
1577 * cells, during DidReflow(), the cell's NotifyPercentBSize() is called
1578 * (the cell is the reflow input's mPercentBSizeObserver in this case).
1579 * NotifyPercentBSize() calls RequestSpecialBSizeReflow().
1581 * XXX (jfkthame) This comment appears to be out of date; it refers to
1582 * methods/flags that are no longer present in the code.
1584 * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true)
1585 * was called, it will do the special bsize reflow, setting the reflow
1586 * input's mFlags.mSpecialBSizeReflow to true and mSpecialHeightInitiator to
1587 * itself. It won't do this if IsPrematureSpecialHeightReflow() returns true
1588 * because in that case another special bsize reflow will be coming along
1589 * with the containing table as the mSpecialHeightInitiator. It is only
1590 * relevant to do the reflow when the mSpecialHeightInitiator is the
1591 * containing table, because if it is a remote ancestor, then appropriate
1592 * bsizes will not be known.
1594 * 3) Since the bsizes of the table, row groups, rows, and cells was determined
1595 * during the pass 2 reflow, they return their last desired sizes during the
1596 * special bsize reflow. The reflow only permits percent bsize frames inside
1597 * the cells to resize based on the cells bsize and that bsize was
1598 * determined during the pass 2 reflow.
1600 * So, in the case of deeply nested tables, all of the tables that were told to
1601 * initiate a special reflow will do so, but if a table is already in a special
1602 * reflow, it won't inititate the reflow until the current initiator is its
1603 * containing table. Since these reflows are only received by frames that need
1604 * them and they don't cause any rebalancing of tables, the extra overhead is
1605 * minimal.
1607 * The type of special reflow that occurs during printing (variety b) follows
1608 * the same mechanism except that all frames will receive the reflow even if
1609 * they don't really need them.
1611 * Open issues with the special bsize reflow:
1613 * 1) At some point there should be 2 kinds of special bsize reflows because (a)
1614 * and (b) above are really quite different. This would avoid unnecessary
1615 * reflows during printing.
1617 * 2) When a cell contains frames whose percent bsizes > 100%, there is data
1618 * loss (see bug 115245). However, this can also occur if a cell has a fixed
1619 * bsize and there is no special bsize reflow.
1621 * XXXldb Special bsize reflow should really be its own method, not
1622 * part of nsIFrame::Reflow. It should then call nsIFrame::Reflow on
1623 * the contents of the cells to do the necessary block-axis resizing.
1625 ******************************************************************************************/
1627 /* Layout the entire inner table. */
1628 void nsTableFrame::Reflow(nsPresContext* aPresContext,
1629 ReflowOutput& aDesiredSize,
1630 const ReflowInput& aReflowInput,
1631 nsReflowStatus& aStatus) {
1632 MarkInReflow();
1633 DO_GLOBAL_REFLOW_COUNT("nsTableFrame");
1634 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1635 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1636 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
1637 "The nsTableWrapperFrame should be the out-of-flow if needed");
1639 const WritingMode wm = aReflowInput.GetWritingMode();
1640 MOZ_ASSERT(aReflowInput.ComputedLogicalMargin(wm).IsAllZero(),
1641 "Only nsTableWrapperFrame can have margins!");
1643 bool isPaginated = aPresContext->IsPaginated();
1645 if (!GetPrevInFlow() && !mTableLayoutStrategy) {
1646 NS_ERROR("strategy should have been created in Init");
1647 return;
1650 // see if collapsing borders need to be calculated
1651 if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) {
1652 CalcBCBorders();
1655 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
1657 // Check for an overflow list, and append any row group frames being pushed
1658 MoveOverflowToChildList();
1660 bool haveDesiredBSize = false;
1661 SetHaveReflowedColGroups(false);
1663 // The tentative width is the width we assumed for the table when the child
1664 // frames were positioned (which only matters in vertical-rl mode, because
1665 // they're positioned relative to the right-hand edge). Then, after reflowing
1666 // the kids, we can check whether the table ends up with a different width
1667 // than this tentative value (either because it was unconstrained, so we used
1668 // zero, or because it was enlarged by the child frames), we make the
1669 // necessary positioning adjustments along the x-axis.
1670 nscoord tentativeContainerWidth = 0;
1671 bool mayAdjustXForAllChildren = false;
1673 // Reflow the entire table (pass 2 and possibly pass 3). This phase is
1674 // necessary during a constrained initial reflow and other reflows which
1675 // require either a strategy init or balance. This isn't done during an
1676 // unconstrained reflow, because it will occur later when the parent reflows
1677 // with a constrained isize.
1678 if (IsSubtreeDirty() || aReflowInput.ShouldReflowAllKids() ||
1679 IsGeometryDirty() || isPaginated || aReflowInput.IsBResize() ||
1680 NeedToCollapse()) {
1681 if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
1682 // Also check IsBResize(), to handle the first Reflow preceding a
1683 // special bsize Reflow, when we've already had a special bsize
1684 // Reflow (where ComputedBSize() would not be
1685 // NS_UNCONSTRAINEDSIZE, but without a style change in between).
1686 aReflowInput.IsBResize()) {
1687 // XXX Eventually, we should modify DistributeBSizeToRows to use
1688 // nsTableRowFrame::GetInitialBSize instead of nsIFrame::BSize().
1689 // That way, it will make its calculations based on internal table
1690 // frame bsizes as they are before they ever had any extra bsize
1691 // distributed to them. In the meantime, this reflows all the
1692 // internal table frames, which restores them to their state before
1693 // DistributeBSizeToRows was called.
1694 SetGeometryDirty();
1697 bool needToInitiateSpecialReflow = false;
1698 if (isPaginated) {
1699 // see if an extra reflow will be necessary in pagination mode
1700 // when there is a specified table bsize
1701 if (!GetPrevInFlow() &&
1702 NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize()) {
1703 LogicalMargin bp = GetChildAreaOffset(wm, &aReflowInput);
1704 nscoord tableSpecifiedBSize =
1705 CalcBorderBoxBSize(aReflowInput, bp, NS_UNCONSTRAINEDSIZE);
1706 if (tableSpecifiedBSize > 0 &&
1707 tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE) {
1708 needToInitiateSpecialReflow = true;
1711 } else {
1712 needToInitiateSpecialReflow =
1713 HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
1715 nsIFrame* lastChildReflowed = nullptr;
1717 NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow,
1718 "Shouldn't be in special bsize reflow here!");
1720 // do the pass 2 reflow unless this is a special bsize reflow and we will be
1721 // initiating a special bsize reflow
1722 // XXXldb I changed this. Should I change it back?
1724 // if we need to initiate a special bsize reflow, then don't constrain the
1725 // bsize of the reflow before that
1726 nscoord availBSize = needToInitiateSpecialReflow
1727 ? NS_UNCONSTRAINEDSIZE
1728 : aReflowInput.AvailableBSize();
1730 ReflowTable(aDesiredSize, aReflowInput, availBSize, lastChildReflowed,
1731 aStatus);
1732 // When in vertical-rl mode, there may be two kinds of scenarios in which
1733 // the positioning of all the children need to be adjusted along the x-axis
1734 // because the width we assumed for the table when the child frames were
1735 // being positioned(i.e. tentative width) may be different from the final
1736 // width for the table:
1737 // 1. If the computed width for the table is unconstrained, a dummy zero
1738 // width was assumed as the tentative width to begin with.
1739 // 2. If the child frames enlarge the width for the table, the final width
1740 // becomes larger than the tentative one.
1741 // Let's record the tentative width here, if later the final width turns out
1742 // to be different from this tentative one, it means one of the above
1743 // scenarios happens, then we adjust positioning of all the children.
1744 // Note that vertical-lr, unlike vertical-rl, doesn't need to take special
1745 // care of this situation, because they're positioned relative to the
1746 // left-hand edge.
1747 if (wm.IsVerticalRL()) {
1748 tentativeContainerWidth =
1749 aReflowInput.ComputedSizeAsContainerIfConstrained().width;
1750 mayAdjustXForAllChildren = true;
1753 // reevaluate special bsize reflow conditions
1754 if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
1755 needToInitiateSpecialReflow = true;
1758 // XXXldb Are all these conditions correct?
1759 if (needToInitiateSpecialReflow && aStatus.IsComplete()) {
1760 // XXXldb Do we need to set the IsBResize flag on any reflow inputs?
1762 ReflowInput& mutable_rs = const_cast<ReflowInput&>(aReflowInput);
1764 // distribute extra block-direction space to rows
1765 CalcDesiredBSize(aReflowInput, aDesiredSize);
1766 mutable_rs.mFlags.mSpecialBSizeReflow = true;
1768 ReflowTable(aDesiredSize, aReflowInput, aReflowInput.AvailableBSize(),
1769 lastChildReflowed, aStatus);
1771 if (lastChildReflowed && aStatus.IsIncomplete()) {
1772 // if there is an incomplete child, then set the desired bsize
1773 // to include it but not the next one
1774 LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
1775 aDesiredSize.BSize(wm) =
1776 borderPadding.BEnd(wm) + GetRowSpacing(GetRowCount()) +
1777 lastChildReflowed->GetNormalRect()
1778 .YMost(); // XXX YMost should be B-flavored
1780 haveDesiredBSize = true;
1782 mutable_rs.mFlags.mSpecialBSizeReflow = false;
1786 aDesiredSize.ISize(wm) =
1787 aReflowInput.ComputedISize() +
1788 aReflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm);
1789 if (!haveDesiredBSize) {
1790 CalcDesiredBSize(aReflowInput, aDesiredSize);
1792 if (IsRowInserted()) {
1793 ProcessRowInserted(aDesiredSize.BSize(wm));
1796 // For more information on the reason for what we should do this, refer to the
1797 // code which defines and evaluates the variables xAdjustmentForAllKids and
1798 // tentativeContainerWidth in the previous part in this function.
1799 if (mayAdjustXForAllChildren) {
1800 nscoord xAdjustmentForAllKids =
1801 aDesiredSize.Width() - tentativeContainerWidth;
1802 if (0 != xAdjustmentForAllKids) {
1803 for (nsIFrame* kid : mFrames) {
1804 kid->MovePositionBy(nsPoint(xAdjustmentForAllKids, 0));
1805 RePositionViews(kid);
1810 // Calculate the overflow area contribution from our children. We couldn't
1811 // do this on the fly during ReflowChildren(), because in vertical-rl mode
1812 // with unconstrained width, we weren't placing them in their final positions
1813 // until the fixupKidPositions loop just above.
1814 for (nsIFrame* kid : mFrames) {
1815 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid);
1818 LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
1819 SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding,
1820 aDesiredSize.PhysicalSize());
1821 NS_WARNING_ASSERTION(NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(),
1822 "reflow branch removed unconstrained available isizes");
1823 if (NeedToCollapse()) {
1824 // This code and the code it depends on assumes that all row groups
1825 // and rows have just been reflowed (i.e., it makes adjustments to
1826 // their rects that are not idempotent). Thus the reflow code
1827 // checks NeedToCollapse() to ensure this is true.
1828 AdjustForCollapsingRowsCols(aDesiredSize, wm, borderPadding);
1831 // If there are any relatively-positioned table parts, we need to reflow their
1832 // absolutely-positioned descendants now that their dimensions are final.
1833 FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowInput);
1835 // make sure the table overflow area does include the table rect.
1836 nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height());
1838 if (ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) !=
1839 PhysicalAxes::Both) {
1840 // collapsed border may leak out
1841 LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm);
1842 tableRect.Inflate(bcMargin.GetPhysicalMargin(wm));
1844 aDesiredSize.mOverflowAreas.UnionAllWith(tableRect);
1846 FinishAndStoreOverflow(&aDesiredSize);
1849 void nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext,
1850 ReflowOutput& aDesiredSize,
1851 const ReflowInput& aReflowInput) {
1852 FrameTArray* positionedParts = GetProperty(PositionedTablePartArray());
1853 if (!positionedParts) {
1854 return;
1857 OverflowChangedTracker overflowTracker;
1858 overflowTracker.SetSubtreeRoot(this);
1860 for (size_t i = 0; i < positionedParts->Length(); ++i) {
1861 nsIFrame* positionedPart = positionedParts->ElementAt(i);
1863 // As we've already finished reflow, positionedParts's size and overflow
1864 // areas have already been assigned, so we just pull them back out.
1865 const WritingMode wm = positionedPart->GetWritingMode();
1866 const LogicalSize size = positionedPart->GetLogicalSize(wm);
1867 ReflowOutput desiredSize(aReflowInput.GetWritingMode());
1868 desiredSize.SetSize(wm, size);
1869 desiredSize.mOverflowAreas =
1870 positionedPart->GetOverflowAreasRelativeToSelf();
1872 // Construct a dummy reflow input and reflow status.
1873 // XXX(seth): Note that the dummy reflow input doesn't have a correct
1874 // chain of parent reflow inputs. It also doesn't necessarily have a
1875 // correct containing block.
1876 LogicalSize availSize = size;
1877 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
1878 ReflowInput reflowInput(aPresContext, positionedPart,
1879 aReflowInput.mRenderingContext, availSize,
1880 ReflowInput::InitFlag::DummyParentReflowInput);
1881 nsReflowStatus reflowStatus;
1883 // Reflow absolutely-positioned descendants of the positioned part.
1884 // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the bsize and
1885 // ignoring any change to the reflow status aren't correct. We'll never
1886 // paginate absolutely positioned frames.
1887 positionedPart->FinishReflowWithAbsoluteFrames(
1888 PresContext(), desiredSize, reflowInput, reflowStatus, true);
1890 // FinishReflowWithAbsoluteFrames has updated overflow on
1891 // |positionedPart|. We need to make sure that update propagates
1892 // through the intermediate frames between it and this frame.
1893 nsIFrame* positionedFrameParent = positionedPart->GetParent();
1894 if (positionedFrameParent != this) {
1895 overflowTracker.AddFrame(positionedFrameParent,
1896 OverflowChangedTracker::CHILDREN_CHANGED);
1900 // Propagate updated overflow areas up the tree.
1901 overflowTracker.Flush();
1903 // Update our own overflow areas. (OverflowChangedTracker doesn't update the
1904 // subtree root itself.)
1905 aDesiredSize.SetOverflowAreasToDesiredBounds();
1906 nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas);
1909 bool nsTableFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
1910 // As above in Reflow, make sure the table overflow area includes the table
1911 // rect, and check for collapsed borders leaking out.
1912 if (ShouldApplyOverflowClipping(StyleDisplay()) != PhysicalAxes::Both) {
1913 nsRect bounds(nsPoint(0, 0), GetSize());
1914 WritingMode wm = GetWritingMode();
1915 LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm);
1916 bounds.Inflate(bcMargin.GetPhysicalMargin(wm));
1918 aOverflowAreas.UnionAllWith(bounds);
1920 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
1923 void nsTableFrame::ReflowTable(ReflowOutput& aDesiredSize,
1924 const ReflowInput& aReflowInput,
1925 nscoord aAvailBSize,
1926 nsIFrame*& aLastChildReflowed,
1927 nsReflowStatus& aStatus) {
1928 aLastChildReflowed = nullptr;
1930 if (!GetPrevInFlow()) {
1931 mTableLayoutStrategy->ComputeColumnISizes(aReflowInput);
1933 // Constrain our reflow isize to the computed table isize (of the 1st in
1934 // flow). and our reflow bsize to our avail bsize minus border, padding,
1935 // cellspacing
1936 WritingMode wm = aReflowInput.GetWritingMode();
1937 aDesiredSize.ISize(wm) =
1938 aReflowInput.ComputedISize() +
1939 aReflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm);
1940 TableReflowInput reflowInput(
1941 aReflowInput, LogicalSize(wm, aDesiredSize.ISize(wm), aAvailBSize));
1942 ReflowChildren(reflowInput, aStatus, aLastChildReflowed,
1943 aDesiredSize.mOverflowAreas);
1945 ReflowColGroups(aReflowInput.mRenderingContext);
1948 nsIFrame* nsTableFrame::GetFirstBodyRowGroupFrame() {
1949 nsIFrame* headerFrame = nullptr;
1950 nsIFrame* footerFrame = nullptr;
1952 for (nsIFrame* kidFrame : mFrames) {
1953 const nsStyleDisplay* childDisplay = kidFrame->StyleDisplay();
1955 // We expect the header and footer row group frames to be first, and we only
1956 // allow one header and one footer
1957 if (mozilla::StyleDisplay::TableHeaderGroup == childDisplay->mDisplay) {
1958 if (headerFrame) {
1959 // We already have a header frame and so this header frame is treated
1960 // like an ordinary body row group frame
1961 return kidFrame;
1963 headerFrame = kidFrame;
1965 } else if (mozilla::StyleDisplay::TableFooterGroup ==
1966 childDisplay->mDisplay) {
1967 if (footerFrame) {
1968 // We already have a footer frame and so this footer frame is treated
1969 // like an ordinary body row group frame
1970 return kidFrame;
1972 footerFrame = kidFrame;
1974 } else if (mozilla::StyleDisplay::TableRowGroup == childDisplay->mDisplay) {
1975 return kidFrame;
1979 return nullptr;
1982 // Table specific version that takes into account repeated header and footer
1983 // frames when continuing table frames
1984 void nsTableFrame::PushChildren(const RowGroupArray& aRowGroups,
1985 int32_t aPushFrom) {
1986 MOZ_ASSERT(aPushFrom > 0, "pushing first child");
1988 // extract the frames from the array into a sibling list
1989 nsFrameList frames;
1990 uint32_t childX;
1991 for (childX = aPushFrom; childX < aRowGroups.Length(); ++childX) {
1992 nsTableRowGroupFrame* rgFrame = aRowGroups[childX];
1993 if (!rgFrame->IsRepeatable()) {
1994 mFrames.RemoveFrame(rgFrame);
1995 frames.AppendFrame(nullptr, rgFrame);
1999 if (frames.IsEmpty()) {
2000 return;
2003 nsTableFrame* nextInFlow = static_cast<nsTableFrame*>(GetNextInFlow());
2004 if (nextInFlow) {
2005 // Insert the frames after any repeated header and footer frames.
2006 nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame();
2007 nsIFrame* prevSibling = nullptr;
2008 if (firstBodyFrame) {
2009 prevSibling = firstBodyFrame->GetPrevSibling();
2011 // When pushing and pulling frames we need to check for whether any
2012 // views need to be reparented.
2013 ReparentFrameViewList(frames, this, nextInFlow);
2014 nextInFlow->mFrames.InsertFrames(nextInFlow, prevSibling,
2015 std::move(frames));
2016 } else {
2017 // Add the frames to our overflow list.
2018 SetOverflowFrames(std::move(frames));
2022 // collapsing row groups, rows, col groups and cols are accounted for after both
2023 // passes of reflow so that it has no effect on the calculations of reflow.
2024 void nsTableFrame::AdjustForCollapsingRowsCols(
2025 ReflowOutput& aDesiredSize, const WritingMode aWM,
2026 const LogicalMargin& aBorderPadding) {
2027 nscoord bTotalOffset = 0; // total offset among all rows in all row groups
2029 // reset the bit, it will be set again if row/rowgroup or col/colgroup are
2030 // collapsed
2031 SetNeedToCollapse(false);
2033 // collapse the rows and/or row groups as necessary
2034 // Get the ordered children
2035 RowGroupArray rowGroups;
2036 OrderRowGroups(rowGroups);
2038 nsTableFrame* firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
2039 nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding);
2040 nscoord rgISize = iSize - GetColSpacing(-1) - GetColSpacing(GetColCount());
2041 OverflowAreas overflow;
2042 // Walk the list of children
2043 for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) {
2044 nsTableRowGroupFrame* rgFrame = rowGroups[childX];
2045 NS_ASSERTION(rgFrame, "Must have row group frame here");
2046 bTotalOffset +=
2047 rgFrame->CollapseRowGroupIfNecessary(bTotalOffset, rgISize, aWM);
2048 ConsiderChildOverflow(overflow, rgFrame);
2051 aDesiredSize.BSize(aWM) -= bTotalOffset;
2052 aDesiredSize.ISize(aWM) = iSize;
2053 overflow.UnionAllWith(
2054 nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()));
2055 FinishAndStoreOverflow(overflow,
2056 nsSize(aDesiredSize.Width(), aDesiredSize.Height()));
2059 nscoord nsTableFrame::GetCollapsedISize(const WritingMode aWM,
2060 const LogicalMargin& aBorderPadding) {
2061 NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedISize called on next in flow");
2062 nscoord iSize = GetColSpacing(GetColCount());
2063 iSize += aBorderPadding.IStartEnd(aWM);
2064 nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
2065 for (nsIFrame* groupFrame : mColGroups) {
2066 const nsStyleVisibility* groupVis = groupFrame->StyleVisibility();
2067 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
2068 nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame;
2069 for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame;
2070 colFrame = colFrame->GetNextCol()) {
2071 const nsStyleDisplay* colDisplay = colFrame->StyleDisplay();
2072 nscoord colIdx = colFrame->GetColIndex();
2073 if (mozilla::StyleDisplay::TableColumn == colDisplay->mDisplay) {
2074 const nsStyleVisibility* colVis = colFrame->StyleVisibility();
2075 bool collapseCol = StyleVisibility::Collapse == colVis->mVisible;
2076 nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
2077 if (!collapseGroup && !collapseCol) {
2078 iSize += colISize;
2079 if (ColumnHasCellSpacingBefore(colIdx)) {
2080 iSize += GetColSpacing(colIdx - 1);
2082 } else {
2083 SetNeedToCollapse(true);
2088 return iSize;
2091 /* virtual */
2092 void nsTableFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
2093 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
2095 if (!aOldComputedStyle) // avoid this on init
2096 return;
2098 if (IsBorderCollapse() && BCRecalcNeeded(aOldComputedStyle, Style())) {
2099 SetFullBCDamageArea();
2102 // avoid this on init or nextinflow
2103 if (!mTableLayoutStrategy || GetPrevInFlow()) return;
2105 bool isAuto = IsAutoLayout();
2106 if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) {
2107 if (isAuto)
2108 mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this);
2109 else
2110 mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this);
2114 void nsTableFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) {
2115 NS_ASSERTION(aListID == FrameChildListID::Principal ||
2116 aListID == FrameChildListID::ColGroup,
2117 "unexpected child list");
2119 // Because we actually have two child lists, one for col group frames and one
2120 // for everything else, we need to look at each frame individually
2121 // XXX The frame construction code should be separating out child frames
2122 // based on the type, bug 343048.
2123 while (!aFrameList.IsEmpty()) {
2124 nsIFrame* f = aFrameList.FirstChild();
2125 aFrameList.RemoveFrame(f);
2127 // See what kind of frame we have
2128 const nsStyleDisplay* display = f->StyleDisplay();
2130 if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
2131 if (MOZ_UNLIKELY(GetPrevInFlow())) {
2132 nsFrameList colgroupFrame(f, f);
2133 auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
2134 firstInFlow->AppendFrames(aListID, std::move(colgroupFrame));
2135 continue;
2137 nsTableColGroupFrame* lastColGroup =
2138 nsTableColGroupFrame::GetLastRealColGroup(this);
2139 int32_t startColIndex = (lastColGroup)
2140 ? lastColGroup->GetStartColumnIndex() +
2141 lastColGroup->GetColCount()
2142 : 0;
2143 mColGroups.InsertFrame(this, lastColGroup, f);
2144 // Insert the colgroup and its cols into the table
2145 InsertColGroups(startColIndex,
2146 nsFrameList::Slice(f, f->GetNextSibling()));
2147 } else if (IsRowGroup(display->mDisplay)) {
2148 DrainSelfOverflowList(); // ensure the last frame is in mFrames
2149 // Append the new row group frame to the sibling chain
2150 mFrames.AppendFrame(nullptr, f);
2152 // insert the row group and its rows into the table
2153 InsertRowGroups(nsFrameList::Slice(f, nullptr));
2154 } else {
2155 // Nothing special to do, just add the frame to our child list
2156 MOZ_ASSERT_UNREACHABLE(
2157 "How did we get here? Frame construction screwed up");
2158 mFrames.AppendFrame(nullptr, f);
2162 #ifdef DEBUG_TABLE_CELLMAP
2163 printf("=== TableFrame::AppendFrames\n");
2164 Dump(true, true, true);
2165 #endif
2166 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
2167 NS_FRAME_HAS_DIRTY_CHILDREN);
2168 SetGeometryDirty();
2171 void nsTableFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
2172 const nsLineList::iterator* aPrevFrameLine,
2173 nsFrameList&& aFrameList) {
2174 // The frames in aFrameList can be a mix of row group frames and col group
2175 // frames. The problem is that they should go in separate child lists so
2176 // we need to deal with that here...
2177 // XXX The frame construction code should be separating out child frames
2178 // based on the type, bug 343048.
2180 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
2181 "inserting after sibling frame with different parent");
2183 if ((aPrevFrame && !aPrevFrame->GetNextSibling()) ||
2184 (!aPrevFrame && GetChildList(aListID).IsEmpty())) {
2185 // Treat this like an append; still a workaround for bug 343048.
2186 AppendFrames(aListID, std::move(aFrameList));
2187 return;
2190 // Collect ColGroupFrames into a separate list and insert those separately
2191 // from the other frames (bug 759249).
2192 nsFrameList colGroupList;
2193 nsFrameList principalList;
2194 do {
2195 const auto display = aFrameList.FirstChild()->StyleDisplay()->mDisplay;
2196 nsFrameList head = aFrameList.Split([display](nsIFrame* aFrame) {
2197 return aFrame->StyleDisplay()->mDisplay != display;
2199 if (display == mozilla::StyleDisplay::TableColumnGroup) {
2200 colGroupList.AppendFrames(nullptr, std::move(head));
2201 } else {
2202 principalList.AppendFrames(nullptr, std::move(head));
2204 } while (aFrameList.NotEmpty());
2206 // We pass aPrevFrame for both ColGroup and other frames since
2207 // HomogenousInsertFrames will only use it if it's a suitable
2208 // prev-sibling for the frames in the frame list.
2209 if (colGroupList.NotEmpty()) {
2210 HomogenousInsertFrames(FrameChildListID::ColGroup, aPrevFrame,
2211 colGroupList);
2213 if (principalList.NotEmpty()) {
2214 HomogenousInsertFrames(FrameChildListID::Principal, aPrevFrame,
2215 principalList);
2219 void nsTableFrame::HomogenousInsertFrames(ChildListID aListID,
2220 nsIFrame* aPrevFrame,
2221 nsFrameList& aFrameList) {
2222 // See what kind of frame we have
2223 const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay();
2224 bool isColGroup =
2225 mozilla::StyleDisplay::TableColumnGroup == display->mDisplay;
2226 #ifdef DEBUG
2227 // Verify that either all siblings have display:table-column-group, or they
2228 // all have display values different from table-column-group.
2229 for (nsIFrame* frame : aFrameList) {
2230 auto nextDisplay = frame->StyleDisplay()->mDisplay;
2231 MOZ_ASSERT(
2232 isColGroup == (nextDisplay == mozilla::StyleDisplay::TableColumnGroup),
2233 "heterogenous childlist");
2235 #endif
2236 if (MOZ_UNLIKELY(isColGroup && GetPrevInFlow())) {
2237 auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
2238 firstInFlow->AppendFrames(aListID, std::move(aFrameList));
2239 return;
2241 if (aPrevFrame) {
2242 const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay();
2243 // Make sure they belong on the same frame list
2244 if ((display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) !=
2245 (prevDisplay->mDisplay == mozilla::StyleDisplay::TableColumnGroup)) {
2246 // the previous frame is not valid, see comment at ::AppendFrames
2247 // XXXbz Using content indices here means XBL will get screwed
2248 // over... Oh, well.
2249 nsIFrame* pseudoFrame = aFrameList.FirstChild();
2250 nsIContent* parentContent = GetContent();
2251 nsIContent* content = nullptr;
2252 aPrevFrame = nullptr;
2253 while (pseudoFrame &&
2254 (parentContent == (content = pseudoFrame->GetContent()))) {
2255 pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
2257 nsCOMPtr<nsIContent> container = content->GetParent();
2258 if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823.
2259 const Maybe<uint32_t> newIndex = container->ComputeIndexOf(content);
2260 nsIFrame* kidFrame;
2261 nsTableColGroupFrame* lastColGroup = nullptr;
2262 if (isColGroup) {
2263 kidFrame = mColGroups.FirstChild();
2264 lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this);
2265 } else {
2266 kidFrame = mFrames.FirstChild();
2268 // Important: need to start at a value smaller than all valid indices
2269 Maybe<uint32_t> lastIndex;
2270 while (kidFrame) {
2271 if (isColGroup) {
2272 if (kidFrame == lastColGroup) {
2273 aPrevFrame =
2274 kidFrame; // there is no real colgroup after this one
2275 break;
2278 pseudoFrame = kidFrame;
2279 while (pseudoFrame &&
2280 (parentContent == (content = pseudoFrame->GetContent()))) {
2281 pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
2283 const Maybe<uint32_t> index = container->ComputeIndexOf(content);
2284 // XXX Keep the odd traditional behavior in some indices are nothing
2285 // cases for now.
2286 if ((index.isSome() &&
2287 (lastIndex.isNothing() || *index > *lastIndex)) &&
2288 (newIndex.isSome() &&
2289 (index.isNothing() || *index < *newIndex))) {
2290 lastIndex = index;
2291 aPrevFrame = kidFrame;
2293 kidFrame = kidFrame->GetNextSibling();
2298 if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
2299 NS_ASSERTION(aListID == FrameChildListID::ColGroup,
2300 "unexpected child list");
2301 // Insert the column group frames
2302 const nsFrameList::Slice& newColgroups =
2303 mColGroups.InsertFrames(this, aPrevFrame, std::move(aFrameList));
2304 // find the starting col index for the first new col group
2305 int32_t startColIndex = 0;
2306 if (aPrevFrame) {
2307 nsTableColGroupFrame* prevColGroup =
2308 (nsTableColGroupFrame*)GetFrameAtOrBefore(
2309 this, aPrevFrame, LayoutFrameType::TableColGroup);
2310 if (prevColGroup) {
2311 startColIndex =
2312 prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount();
2315 InsertColGroups(startColIndex, newColgroups);
2316 } else if (IsRowGroup(display->mDisplay)) {
2317 NS_ASSERTION(aListID == FrameChildListID::Principal,
2318 "unexpected child list");
2319 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
2320 // Insert the frames in the sibling chain
2321 const nsFrameList::Slice& newRowGroups =
2322 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
2324 InsertRowGroups(newRowGroups);
2325 } else {
2326 NS_ASSERTION(aListID == FrameChildListID::Principal,
2327 "unexpected child list");
2328 MOZ_ASSERT_UNREACHABLE("How did we even get here?");
2329 // Just insert the frame and don't worry about reflowing it
2330 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList));
2331 return;
2334 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
2335 NS_FRAME_HAS_DIRTY_CHILDREN);
2336 SetGeometryDirty();
2337 #ifdef DEBUG_TABLE_CELLMAP
2338 printf("=== TableFrame::InsertFrames\n");
2339 Dump(true, true, true);
2340 #endif
2343 void nsTableFrame::DoRemoveFrame(DestroyContext& aContext, ChildListID aListID,
2344 nsIFrame* aOldFrame) {
2345 if (aListID == FrameChildListID::ColGroup) {
2346 nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling();
2347 nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame;
2348 int32_t firstColIndex = colGroup->GetStartColumnIndex();
2349 int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1;
2350 mColGroups.DestroyFrame(aContext, aOldFrame);
2351 nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex);
2352 // remove the cols from the table
2353 int32_t colIdx;
2354 for (colIdx = lastColIndex; colIdx >= firstColIndex; colIdx--) {
2355 nsTableColFrame* colFrame = mColFrames.SafeElementAt(colIdx);
2356 if (colFrame) {
2357 RemoveCol(colGroup, colIdx, true, false);
2361 // If we have some anonymous cols at the end already, we just
2362 // add more of them.
2363 if (!mColFrames.IsEmpty() &&
2364 mColFrames.LastElement() && // XXXbz is this ever null?
2365 mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
2366 int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length();
2367 if (numAnonymousColsToAdd > 0) {
2368 // this sets the child list, updates the col cache and cell map
2369 AppendAnonymousColFrames(numAnonymousColsToAdd);
2371 } else {
2372 // All of our colframes correspond to actual <col> tags. It's possible
2373 // that we still have at least as many <col> tags as we have logical
2374 // columns from cells, but we might have one less. Handle the latter case
2375 // as follows: First ask the cellmap to drop its last col if it doesn't
2376 // have any actual cells in it. Then call MatchCellMapToColCache to
2377 // append an anonymous column if it's needed; this needs to be after
2378 // RemoveColsAtEnd, since it will determine the need for a new column
2379 // frame based on the width of the cell map.
2380 nsTableCellMap* cellMap = GetCellMap();
2381 if (cellMap) { // XXXbz is this ever null?
2382 cellMap->RemoveColsAtEnd();
2383 MatchCellMapToColCache(cellMap);
2387 } else {
2388 NS_ASSERTION(aListID == FrameChildListID::Principal,
2389 "unexpected child list");
2390 nsTableRowGroupFrame* rgFrame =
2391 static_cast<nsTableRowGroupFrame*>(aOldFrame);
2392 // remove the row group from the cell map
2393 nsTableCellMap* cellMap = GetCellMap();
2394 if (cellMap) {
2395 cellMap->RemoveGroupCellMap(rgFrame);
2398 // remove the row group frame from the sibling chain
2399 mFrames.DestroyFrame(aContext, aOldFrame);
2401 // the removal of a row group changes the cellmap, the columns might change
2402 if (cellMap) {
2403 cellMap->Synchronize(this);
2404 // Create an empty slice
2405 ResetRowIndices(nsFrameList::Slice(nullptr, nullptr));
2406 TableArea damageArea;
2407 cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false,
2408 damageArea);
2410 static_cast<nsTableFrame*>(FirstInFlow())
2411 ->MatchCellMapToColCache(cellMap);
2416 void nsTableFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID,
2417 nsIFrame* aOldFrame) {
2418 NS_ASSERTION(aListID == FrameChildListID::ColGroup ||
2419 mozilla::StyleDisplay::TableColumnGroup !=
2420 aOldFrame->StyleDisplay()->mDisplay,
2421 "Wrong list name; use FrameChildListID::ColGroup iff colgroup");
2422 mozilla::PresShell* presShell = PresShell();
2423 nsTableFrame* lastParent = nullptr;
2424 while (aOldFrame) {
2425 nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation();
2426 nsTableFrame* parent = static_cast<nsTableFrame*>(aOldFrame->GetParent());
2427 if (parent != lastParent) {
2428 parent->DrainSelfOverflowList();
2430 parent->DoRemoveFrame(aContext, aListID, aOldFrame);
2431 aOldFrame = oldFrameNextContinuation;
2432 if (parent != lastParent) {
2433 // for now, just bail and recalc all of the collapsing borders
2434 // as the cellmap changes we need to recalc
2435 if (parent->IsBorderCollapse()) {
2436 parent->SetFullBCDamageArea();
2438 parent->SetGeometryDirty();
2439 presShell->FrameNeedsReflow(parent, IntrinsicDirty::FrameAndAncestors,
2440 NS_FRAME_HAS_DIRTY_CHILDREN);
2441 lastParent = parent;
2444 #ifdef DEBUG_TABLE_CELLMAP
2445 printf("=== TableFrame::RemoveFrame\n");
2446 Dump(true, true, true);
2447 #endif
2450 /* virtual */
2451 nsMargin nsTableFrame::GetUsedBorder() const {
2452 if (!IsBorderCollapse()) return nsContainerFrame::GetUsedBorder();
2454 WritingMode wm = GetWritingMode();
2455 return GetIncludedOuterBCBorder(wm).GetPhysicalMargin(wm);
2458 /* virtual */
2459 nsMargin nsTableFrame::GetUsedPadding() const {
2460 if (!IsBorderCollapse()) return nsContainerFrame::GetUsedPadding();
2462 return nsMargin(0, 0, 0, 0);
2465 /* virtual */
2466 nsMargin nsTableFrame::GetUsedMargin() const {
2467 // The margin is inherited to the table wrapper frame via
2468 // the ::-moz-table-wrapper rule in ua.css.
2469 return nsMargin(0, 0, 0, 0);
2472 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCProperty, BCPropertyData)
2474 BCPropertyData* nsTableFrame::GetBCProperty() const {
2475 return GetProperty(TableBCProperty());
2478 BCPropertyData* nsTableFrame::GetOrCreateBCProperty() {
2479 BCPropertyData* value = GetProperty(TableBCProperty());
2480 if (!value) {
2481 value = new BCPropertyData();
2482 SetProperty(TableBCProperty(), value);
2485 return value;
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 BCPropertyData* propData = GetBCProperty();
2500 if (propData) {
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 BCPropertyData* propData = GetBCProperty();
2518 if (propData) {
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 static LogicalMargin GetSeparateModelBorderPadding(
2534 const WritingMode aWM, const ReflowInput* aReflowInput,
2535 ComputedStyle* aComputedStyle) {
2536 // XXXbz Either we _do_ have a reflow input and then we can use its
2537 // mComputedBorderPadding or we don't and then we get the padding
2538 // wrong!
2539 const nsStyleBorder* border = aComputedStyle->StyleBorder();
2540 LogicalMargin borderPadding(aWM, border->GetComputedBorder());
2541 if (aReflowInput) {
2542 borderPadding += aReflowInput->ComputedLogicalPadding(aWM);
2544 return borderPadding;
2547 void nsTableFrame::GetCollapsedBorderPadding(
2548 Maybe<LogicalMargin>& aBorder, Maybe<LogicalMargin>& aPadding) const {
2549 if (IsBorderCollapse()) {
2550 // Border-collapsed tables don't use any of their padding, and only part of
2551 // their border.
2552 const auto wm = GetWritingMode();
2553 aBorder.emplace(GetIncludedOuterBCBorder(wm));
2554 aPadding.emplace(wm);
2558 LogicalMargin nsTableFrame::GetChildAreaOffset(
2559 const WritingMode aWM, const ReflowInput* aReflowInput) const {
2560 return IsBorderCollapse()
2561 ? GetIncludedOuterBCBorder(aWM)
2562 : GetSeparateModelBorderPadding(aWM, aReflowInput, mComputedStyle);
2565 void nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput) {
2566 const auto childWM = aReflowInput.GetWritingMode();
2567 LogicalMargin border(childWM);
2568 if (IsBorderCollapse()) {
2569 nsTableRowGroupFrame* rgFrame =
2570 static_cast<nsTableRowGroupFrame*>(aReflowInput.mFrame);
2571 border = rgFrame->GetBCBorderWidth(childWM);
2573 const LogicalMargin zeroPadding(childWM);
2574 aReflowInput.Init(PresContext(), Nothing(), Some(border), Some(zeroPadding));
2576 NS_ASSERTION(!mBits.mResizedColumns ||
2577 !aReflowInput.mParentReflowInput->mFlags.mSpecialBSizeReflow,
2578 "should not resize columns on special bsize reflow");
2579 if (mBits.mResizedColumns) {
2580 aReflowInput.SetIResize(true);
2584 // Position and size aKidFrame and update our reflow input. The origin of
2585 // aKidRect is relative to the upper-left origin of our frame
2586 void nsTableFrame::PlaceChild(TableReflowInput& aReflowInput,
2587 nsIFrame* aKidFrame,
2588 const ReflowInput& aKidReflowInput,
2589 const mozilla::LogicalPoint& aKidPosition,
2590 const nsSize& aContainerSize,
2591 ReflowOutput& aKidDesiredSize,
2592 const nsRect& aOriginalKidRect,
2593 const nsRect& aOriginalKidInkOverflow) {
2594 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode();
2595 bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
2597 // Place and size the child
2598 FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, &aKidReflowInput,
2599 wm, aKidPosition, aContainerSize,
2600 ReflowChildFlags::ApplyRelativePositioning);
2602 InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidInkOverflow,
2603 isFirstReflow);
2605 // Adjust the running block-offset
2606 aReflowInput.mBCoord += aKidDesiredSize.BSize(wm);
2608 // If our bsize is constrained, then update the available bsize
2609 aReflowInput.ReduceAvailableBSizeBy(wm, aKidDesiredSize.BSize(wm));
2612 void nsTableFrame::OrderRowGroups(RowGroupArray& aChildren,
2613 nsTableRowGroupFrame** aHead,
2614 nsTableRowGroupFrame** aFoot) const {
2615 aChildren.Clear();
2616 nsTableRowGroupFrame* head = nullptr;
2617 nsTableRowGroupFrame* foot = nullptr;
2619 nsIFrame* kidFrame = mFrames.FirstChild();
2620 while (kidFrame) {
2621 const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay();
2622 nsTableRowGroupFrame* rowGroup =
2623 static_cast<nsTableRowGroupFrame*>(kidFrame);
2625 switch (kidDisplay->DisplayInside()) {
2626 case StyleDisplayInside::TableHeaderGroup:
2627 if (head) { // treat additional thead like tbody
2628 aChildren.AppendElement(rowGroup);
2629 } else {
2630 head = rowGroup;
2632 break;
2633 case StyleDisplayInside::TableFooterGroup:
2634 if (foot) { // treat additional tfoot like tbody
2635 aChildren.AppendElement(rowGroup);
2636 } else {
2637 foot = rowGroup;
2639 break;
2640 case StyleDisplayInside::TableRowGroup:
2641 aChildren.AppendElement(rowGroup);
2642 break;
2643 default:
2644 MOZ_ASSERT_UNREACHABLE("How did this produce an nsTableRowGroupFrame?");
2645 // Just ignore it
2646 break;
2648 // Get the next sibling but skip it if it's also the next-in-flow, since
2649 // a next-in-flow will not be part of the current table.
2650 while (kidFrame) {
2651 nsIFrame* nif = kidFrame->GetNextInFlow();
2652 kidFrame = kidFrame->GetNextSibling();
2653 if (kidFrame != nif) break;
2657 // put the thead first
2658 if (head) {
2659 aChildren.InsertElementAt(0, head);
2661 if (aHead) *aHead = head;
2662 // put the tfoot after the last tbody
2663 if (foot) {
2664 aChildren.AppendElement(foot);
2666 if (aFoot) *aFoot = foot;
2669 static bool IsRepeatable(nscoord aFrameHeight, nscoord aPageHeight) {
2670 return aFrameHeight < (aPageHeight / 4);
2673 nscoord nsTableFrame::SetupHeaderFooterChild(
2674 const TableReflowInput& aReflowInput, nsTableRowGroupFrame* aFrame) {
2675 nsPresContext* presContext = PresContext();
2676 nscoord pageHeight = presContext->GetPageSize().height;
2678 // Reflow the child with unconstrained height
2679 WritingMode wm = aFrame->GetWritingMode();
2680 LogicalSize availSize = aReflowInput.mReflowInput.AvailableSize(wm);
2682 nsSize containerSize = availSize.GetPhysicalSize(wm);
2683 // XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE
2685 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
2686 ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput, aFrame,
2687 availSize, Nothing(),
2688 ReflowInput::InitFlag::CallerWillInit);
2689 InitChildReflowInput(kidReflowInput);
2690 kidReflowInput.mFlags.mIsTopOfPage = true;
2691 ReflowOutput desiredSize(aReflowInput.mReflowInput);
2692 nsReflowStatus status;
2693 ReflowChild(aFrame, presContext, desiredSize, kidReflowInput, wm,
2694 LogicalPoint(wm, aReflowInput.mICoord, aReflowInput.mBCoord),
2695 containerSize, ReflowChildFlags::Default, status);
2696 // The child will be reflowed again "for real" so no need to place it now
2698 aFrame->SetRepeatable(IsRepeatable(desiredSize.Height(), pageHeight));
2699 return desiredSize.Height();
2702 void nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput,
2703 nsTableRowGroupFrame* aTfoot,
2704 nscoord aFooterHeight) {
2705 nsPresContext* presContext = PresContext();
2706 WritingMode wm = aTfoot->GetWritingMode();
2707 LogicalSize kidAvailSize = aReflowInput.mAvailSize;
2709 nsSize containerSize = kidAvailSize.GetPhysicalSize(wm);
2710 // XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE
2712 kidAvailSize.BSize(wm) = aFooterHeight;
2713 ReflowInput footerReflowInput(presContext, aReflowInput.mReflowInput, aTfoot,
2714 kidAvailSize, Nothing(),
2715 ReflowInput::InitFlag::CallerWillInit);
2716 InitChildReflowInput(footerReflowInput);
2717 aReflowInput.mBCoord += GetRowSpacing(GetRowCount());
2719 nsRect origTfootRect = aTfoot->GetRect();
2720 nsRect origTfootInkOverflow = aTfoot->InkOverflowRect();
2722 nsReflowStatus footerStatus;
2723 ReflowOutput desiredSize(aReflowInput.mReflowInput);
2724 LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord);
2725 ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput, wm,
2726 kidPosition, containerSize, ReflowChildFlags::Default,
2727 footerStatus);
2729 PlaceChild(aReflowInput, aTfoot, footerReflowInput, kidPosition,
2730 containerSize, desiredSize, origTfootRect, origTfootInkOverflow);
2733 // Reflow the children based on the avail size and reason in aReflowInput
2734 void nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput,
2735 nsReflowStatus& aStatus,
2736 nsIFrame*& aLastChildReflowed,
2737 OverflowAreas& aOverflowAreas) {
2738 aStatus.Reset();
2739 aLastChildReflowed = nullptr;
2741 nsIFrame* prevKidFrame = nullptr;
2742 WritingMode wm = aReflowInput.mReflowInput.GetWritingMode();
2743 NS_WARNING_ASSERTION(
2744 wm.IsVertical() ||
2745 NS_UNCONSTRAINEDSIZE != aReflowInput.mReflowInput.ComputedWidth(),
2746 "shouldn't have unconstrained width in horizontal mode");
2747 nsSize containerSize =
2748 aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained();
2750 nsPresContext* presContext = PresContext();
2751 // nsTableFrame is not able to pull back children from its next-in-flow, per
2752 // bug 1772383. So even under paginated contexts, tables should not fragment
2753 // if they are inside of (i.e. potentially being fragmented by) a column-set
2754 // frame. (This is indicated by the "mTableIsSplittable" flag.)
2755 bool isPaginated =
2756 presContext->IsPaginated() &&
2757 NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm) &&
2758 aReflowInput.mReflowInput.mFlags.mTableIsSplittable;
2760 // Tables currently (though we ought to fix this) only fragment in
2761 // paginated contexts, not in multicolumn contexts. (See bug 888257.)
2762 // This is partly because they don't correctly handle incremental
2763 // layout when paginated.
2765 // Since we propagate NS_FRAME_IS_DIRTY from parent to child at the
2766 // start of the parent's reflow (behavior that's new as of bug
2767 // 1308876), we can do things that are effectively incremental reflow
2768 // during paginated layout. Since the table code doesn't handle this
2769 // correctly, we need to set the flag that says to reflow everything
2770 // within the table structure.
2771 if (presContext->IsPaginated()) {
2772 SetGeometryDirty();
2775 aOverflowAreas.Clear();
2777 bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() ||
2778 mBits.mResizedColumns || IsGeometryDirty() ||
2779 NeedToCollapse();
2781 RowGroupArray rowGroups;
2782 nsTableRowGroupFrame *thead, *tfoot;
2783 OrderRowGroups(rowGroups, &thead, &tfoot);
2784 bool pageBreak = false;
2785 nscoord footerHeight = 0;
2787 // Determine the repeatablility of headers and footers, and also the desired
2788 // height of any repeatable footer.
2789 // The repeatability of headers on continued tables is handled
2790 // when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame.
2791 // We handle the repeatability of footers again here because we need to
2792 // determine the footer's height anyway. We could perhaps optimize by
2793 // using the footer's prev-in-flow's height instead of reflowing it again,
2794 // but there's no real need.
2795 if (isPaginated) {
2796 bool reorder = false;
2797 if (thead && !GetPrevInFlow()) {
2798 reorder = thead->GetNextInFlow();
2799 SetupHeaderFooterChild(aReflowInput, thead);
2801 if (tfoot) {
2802 reorder = reorder || tfoot->GetNextInFlow();
2803 footerHeight = SetupHeaderFooterChild(aReflowInput, tfoot);
2805 if (reorder) {
2806 // Reorder row groups - the reflow may have changed the nextinflows.
2807 OrderRowGroups(rowGroups, &thead, &tfoot);
2810 // if the child is a tbody in paginated mode reduce the height by a repeated
2811 // footer
2812 bool allowRepeatedFooter = false;
2813 for (size_t childX = 0; childX < rowGroups.Length(); childX++) {
2814 nsTableRowGroupFrame* kidFrame = rowGroups[childX];
2815 const nscoord cellSpacingB =
2816 GetRowSpacing(kidFrame->GetStartRowIndex() + kidFrame->GetRowCount());
2817 // Get the frame state bits
2818 // See if we should only reflow the dirty child frames
2819 if (reflowAllKids || kidFrame->IsSubtreeDirty() ||
2820 (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow &&
2821 (isPaginated ||
2822 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
2823 if (pageBreak) {
2824 if (allowRepeatedFooter) {
2825 PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
2826 } else if (tfoot && tfoot->IsRepeatable()) {
2827 tfoot->SetRepeatable(false);
2829 PushChildren(rowGroups, childX);
2830 aStatus.Reset();
2831 aStatus.SetIncomplete();
2832 break;
2835 LogicalSize kidAvailSize(aReflowInput.mAvailSize);
2836 allowRepeatedFooter = false;
2837 if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) {
2838 nsTableRowGroupFrame* kidRG =
2839 static_cast<nsTableRowGroupFrame*>(kidFrame);
2840 if (kidRG != thead && kidRG != tfoot && tfoot &&
2841 tfoot->IsRepeatable()) {
2842 // the child is a tbody and there is a repeatable footer
2843 NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1],
2844 "Missing footer!");
2845 if (footerHeight + cellSpacingB < kidAvailSize.BSize(wm)) {
2846 allowRepeatedFooter = true;
2847 kidAvailSize.BSize(wm) -= footerHeight + cellSpacingB;
2852 nsRect oldKidRect = kidFrame->GetRect();
2853 nsRect oldKidInkOverflow = kidFrame->InkOverflowRect();
2855 ReflowOutput desiredSize(aReflowInput.mReflowInput);
2857 // Reflow the child into the available space
2858 ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput,
2859 kidFrame, kidAvailSize, Nothing(),
2860 ReflowInput::InitFlag::CallerWillInit);
2861 InitChildReflowInput(kidReflowInput);
2863 // If this isn't the first row group, and the previous row group has a
2864 // nonzero YMost, then we can't be at the top of the page.
2865 // We ignore a repeated head row group in this check to avoid causing
2866 // infinite loops in some circumstances - see bug 344883.
2867 if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) &&
2868 (rowGroups[childX - 1]->GetNormalRect().YMost() > 0)) {
2869 kidReflowInput.mFlags.mIsTopOfPage = false;
2871 aReflowInput.mBCoord += cellSpacingB;
2872 aReflowInput.ReduceAvailableBSizeBy(wm, cellSpacingB);
2873 // record the presence of a next in flow, it might get destroyed so we
2874 // need to reorder the row group array
2875 const bool reorder = kidFrame->GetNextInFlow();
2877 LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord);
2878 aStatus.Reset();
2879 ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput, wm,
2880 kidPosition, containerSize, ReflowChildFlags::Default,
2881 aStatus);
2883 if (reorder) {
2884 // Reorder row groups - the reflow may have changed the nextinflows.
2885 OrderRowGroups(rowGroups, &thead, &tfoot);
2886 childX = rowGroups.IndexOf(kidFrame);
2887 if (childX == RowGroupArray::NoIndex) {
2888 // XXXbz can this happen?
2889 childX = rowGroups.Length();
2892 if (isPaginated && !aStatus.IsFullyComplete() &&
2893 ShouldAvoidBreakInside(aReflowInput.mReflowInput)) {
2894 aStatus.SetInlineLineBreakBeforeAndReset();
2895 break;
2897 // see if the rowgroup did not fit on this page might be pushed on
2898 // the next page
2899 if (isPaginated &&
2900 (aStatus.IsInlineBreakBefore() ||
2901 (aStatus.IsComplete() &&
2902 (NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight()) &&
2903 kidReflowInput.AvailableHeight() < desiredSize.Height()))) {
2904 if (ShouldAvoidBreakInside(aReflowInput.mReflowInput)) {
2905 aStatus.SetInlineLineBreakBeforeAndReset();
2906 break;
2908 // if we are on top of the page place with dataloss
2909 if (kidReflowInput.mFlags.mIsTopOfPage) {
2910 if (childX + 1 < rowGroups.Length()) {
2911 nsIFrame* nextRowGroupFrame = rowGroups[childX + 1];
2912 if (nextRowGroupFrame) {
2913 PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition,
2914 containerSize, desiredSize, oldKidRect,
2915 oldKidInkOverflow);
2916 if (allowRepeatedFooter) {
2917 PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
2918 } else if (tfoot && tfoot->IsRepeatable()) {
2919 tfoot->SetRepeatable(false);
2921 aStatus.Reset();
2922 aStatus.SetIncomplete();
2923 PushChildren(rowGroups, childX + 1);
2924 aLastChildReflowed = kidFrame;
2925 break;
2928 } else { // we are not on top, push this rowgroup onto the next page
2929 if (prevKidFrame) { // we had a rowgroup before so push this
2930 if (allowRepeatedFooter) {
2931 PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
2932 } else if (tfoot && tfoot->IsRepeatable()) {
2933 tfoot->SetRepeatable(false);
2935 aStatus.Reset();
2936 aStatus.SetIncomplete();
2937 PushChildren(rowGroups, childX);
2938 aLastChildReflowed = prevKidFrame;
2939 break;
2940 } else { // we can't push so lets make clear how much space we need
2941 PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition,
2942 containerSize, desiredSize, oldKidRect,
2943 oldKidInkOverflow);
2944 aLastChildReflowed = kidFrame;
2945 if (allowRepeatedFooter) {
2946 PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
2947 aLastChildReflowed = tfoot;
2949 break;
2954 aLastChildReflowed = kidFrame;
2956 pageBreak = false;
2957 // see if there is a page break after this row group or before the next
2958 // one
2959 if (aStatus.IsComplete() && isPaginated &&
2960 (NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight())) {
2961 nsIFrame* nextKid =
2962 (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr;
2963 pageBreak = PageBreakAfter(kidFrame, nextKid);
2966 // Place the child
2967 PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition,
2968 containerSize, desiredSize, oldKidRect, oldKidInkOverflow);
2970 // Remember where we just were in case we end up pushing children
2971 prevKidFrame = kidFrame;
2973 MOZ_ASSERT(!aStatus.IsIncomplete() || isPaginated,
2974 "Table contents should only fragment in paginated contexts");
2976 // Special handling for incomplete children
2977 if (isPaginated && aStatus.IsIncomplete()) {
2978 nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();
2979 if (!kidNextInFlow) {
2980 // The child doesn't have a next-in-flow so create a continuing
2981 // frame. This hooks the child into the flow
2982 kidNextInFlow =
2983 PresShell()->FrameConstructor()->CreateContinuingFrame(kidFrame,
2984 this);
2986 // Insert the kid's new next-in-flow into our sibling list...
2987 mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow);
2988 // and in rowGroups after childX so that it will get pushed below.
2989 rowGroups.InsertElementAt(
2990 childX + 1, static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
2991 } else if (kidNextInFlow == kidFrame->GetNextSibling()) {
2992 // OrderRowGroups excludes NIFs in the child list from 'rowGroups'
2993 // so we deal with that here to make sure they get pushed.
2994 MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow),
2995 "OrderRowGroups must not put our NIF in 'rowGroups'");
2996 rowGroups.InsertElementAt(
2997 childX + 1, static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
3000 // We've used up all of our available space so push the remaining
3001 // children.
3002 if (allowRepeatedFooter) {
3003 PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
3004 } else if (tfoot && tfoot->IsRepeatable()) {
3005 tfoot->SetRepeatable(false);
3008 nsIFrame* nextSibling = kidFrame->GetNextSibling();
3009 if (nextSibling) {
3010 PushChildren(rowGroups, childX + 1);
3012 break;
3014 } else { // it isn't being reflowed
3015 aReflowInput.mBCoord += cellSpacingB;
3016 const LogicalRect kidRect =
3017 kidFrame->GetLogicalNormalRect(wm, containerSize);
3018 if (kidRect.BStart(wm) != aReflowInput.mBCoord) {
3019 // invalidate the old position
3020 kidFrame->InvalidateFrameSubtree();
3021 // move to the new position
3022 kidFrame->MovePositionBy(
3023 wm, LogicalPoint(wm, 0, aReflowInput.mBCoord - kidRect.BStart(wm)));
3024 RePositionViews(kidFrame);
3025 // invalidate the new position
3026 kidFrame->InvalidateFrameSubtree();
3028 aReflowInput.mBCoord += kidRect.BSize(wm);
3030 aReflowInput.ReduceAvailableBSizeBy(wm, cellSpacingB + kidRect.BSize(wm));
3034 // We've now propagated the column resizes and geometry changes to all
3035 // the children.
3036 mBits.mResizedColumns = false;
3037 ClearGeometryDirty();
3039 // nsTableFrame does not pull children from its next-in-flow (bug 1772383).
3040 // This is generally fine, since tables only fragment for printing
3041 // (bug 888257) where incremental-reflow is impossible, and so children don't
3042 // usually dynamically move back and forth between continuations. However,
3043 // there are edge cases even with printing where nsTableFrame:
3044 // (1) Generates a continuation and passes children to it,
3045 // (2) Receives another call to Reflow, during which it
3046 // (3) Successfully lays out its remaining children.
3047 // If the completed status flows up as-is, the continuation will be destroyed.
3048 // To avoid that, we return an incomplete status if the continuation contains
3049 // any child that is not a repeated frame.
3050 auto hasNextInFlowThatMustBePreserved = [this, isPaginated]() -> bool {
3051 if (!isPaginated) {
3052 return false;
3054 auto* nextInFlow = static_cast<nsTableFrame*>(GetNextInFlow());
3055 if (!nextInFlow) {
3056 return false;
3058 for (nsIFrame* kidFrame : nextInFlow->mFrames) {
3059 if (!IsRepeatedFrame(kidFrame)) {
3060 return true;
3063 return false;
3065 if (aStatus.IsComplete() && hasNextInFlowThatMustBePreserved()) {
3066 aStatus.SetIncomplete();
3070 void nsTableFrame::ReflowColGroups(gfxContext* aRenderingContext) {
3071 if (!GetPrevInFlow() && !HaveReflowedColGroups()) {
3072 ReflowOutput kidMet(GetWritingMode());
3073 nsPresContext* presContext = PresContext();
3074 for (nsIFrame* kidFrame : mColGroups) {
3075 if (kidFrame->IsSubtreeDirty()) {
3076 // The column groups don't care about dimensions or reflow inputs.
3077 ReflowInput kidReflowInput(presContext, kidFrame, aRenderingContext,
3078 LogicalSize(kidFrame->GetWritingMode()));
3079 nsReflowStatus cgStatus;
3080 ReflowChild(kidFrame, presContext, kidMet, kidReflowInput, 0, 0,
3081 ReflowChildFlags::Default, cgStatus);
3082 FinishReflowChild(kidFrame, presContext, kidMet, &kidReflowInput, 0, 0,
3083 ReflowChildFlags::Default);
3086 SetHaveReflowedColGroups(true);
3090 void nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput,
3091 ReflowOutput& aDesiredSize) {
3092 WritingMode wm = aReflowInput.GetWritingMode();
3093 nsTableCellMap* cellMap = GetCellMap();
3094 if (!cellMap) {
3095 NS_ERROR("never ever call me until the cell map is built!");
3096 aDesiredSize.BSize(wm) = 0;
3097 return;
3099 LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
3101 // get the natural bsize based on the last child's (row group) rect
3102 RowGroupArray rowGroups;
3103 OrderRowGroups(rowGroups);
3104 nscoord desiredBSize = borderPadding.BStartEnd(wm);
3105 if (rowGroups.IsEmpty()) {
3106 if (eCompatibility_NavQuirks == PresContext()->CompatibilityMode()) {
3107 // empty tables should not have a size in quirks mode
3108 aDesiredSize.BSize(wm) = 0;
3109 } else {
3110 aDesiredSize.BSize(wm) =
3111 CalcBorderBoxBSize(aReflowInput, borderPadding, desiredBSize);
3113 return;
3115 int32_t rowCount = cellMap->GetRowCount();
3116 int32_t colCount = cellMap->GetColCount();
3117 if (rowCount > 0 && colCount > 0) {
3118 desiredBSize += GetRowSpacing(-1);
3119 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
3120 desiredBSize += rowGroups[rgIdx]->BSize(wm) +
3121 GetRowSpacing(rowGroups[rgIdx]->GetRowCount() +
3122 rowGroups[rgIdx]->GetStartRowIndex());
3126 // see if a specified table bsize requires dividing additional space to rows
3127 if (!GetPrevInFlow()) {
3128 nscoord bSize =
3129 CalcBorderBoxBSize(aReflowInput, borderPadding, desiredBSize);
3130 if (bSize > desiredBSize) {
3131 // proportionately distribute the excess bsize to unconstrained rows in
3132 // each unconstrained row group.
3133 DistributeBSizeToRows(aReflowInput, bSize - desiredBSize);
3134 // this might have changed the overflow area incorporate the childframe
3135 // overflow area.
3136 for (nsIFrame* kidFrame : mFrames) {
3137 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
3139 aDesiredSize.BSize(wm) = bSize;
3140 } else {
3141 // Tables don't shrink below their intrinsic size, apparently, even when
3142 // constrained by stuff like flex / grid or what not.
3143 aDesiredSize.BSize(wm) = desiredBSize;
3145 } else {
3146 // FIXME(emilio): Is this right? This only affects fragmented tables...
3147 aDesiredSize.BSize(wm) = desiredBSize;
3151 static void ResizeCells(nsTableFrame& aTableFrame) {
3152 nsTableFrame::RowGroupArray rowGroups;
3153 aTableFrame.OrderRowGroups(rowGroups);
3154 WritingMode wm = aTableFrame.GetWritingMode();
3155 ReflowOutput tableDesiredSize(wm);
3156 tableDesiredSize.SetSize(wm, aTableFrame.GetLogicalSize(wm));
3157 tableDesiredSize.SetOverflowAreasToDesiredBounds();
3159 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
3160 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
3162 ReflowOutput groupDesiredSize(wm);
3163 groupDesiredSize.SetSize(wm, rgFrame->GetLogicalSize(wm));
3164 groupDesiredSize.SetOverflowAreasToDesiredBounds();
3166 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
3167 while (rowFrame) {
3168 rowFrame->DidResize();
3169 rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame);
3170 rowFrame = rowFrame->GetNextRow();
3172 rgFrame->FinishAndStoreOverflow(&groupDesiredSize);
3173 tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas +
3174 rgFrame->GetPosition());
3176 aTableFrame.FinishAndStoreOverflow(&tableDesiredSize);
3179 void nsTableFrame::DistributeBSizeToRows(const ReflowInput& aReflowInput,
3180 nscoord aAmount) {
3181 WritingMode wm = aReflowInput.GetWritingMode();
3182 LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
3184 nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
3186 RowGroupArray rowGroups;
3187 OrderRowGroups(rowGroups);
3189 nscoord amountUsed = 0;
3190 // distribute space to each pct bsize row whose row group doesn't have a
3191 // computed bsize, and base the pct on the table bsize. If the row group had a
3192 // computed bsize, then this was already done in
3193 // nsTableRowGroupFrame::CalculateRowBSizes
3194 nscoord pctBasis =
3195 aReflowInput.ComputedBSize() - GetRowSpacing(-1, GetRowCount());
3196 nscoord bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(0);
3197 nscoord bEndRG = bOriginRG;
3198 uint32_t rgIdx;
3199 for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
3200 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
3201 nscoord amountUsedByRG = 0;
3202 nscoord bOriginRow = 0;
3203 const LogicalRect rgNormalRect =
3204 rgFrame->GetLogicalNormalRect(wm, containerSize);
3205 if (!rgFrame->HasStyleBSize()) {
3206 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
3207 while (rowFrame) {
3208 // We don't know the final width of the rowGroupFrame yet, so use 0,0
3209 // as a dummy containerSize here; we'll adjust the row positions at
3210 // the end, after the rowGroup size is finalized.
3211 const nsSize dummyContainerSize;
3212 const LogicalRect rowNormalRect =
3213 rowFrame->GetLogicalNormalRect(wm, dummyContainerSize);
3214 nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex());
3215 if ((amountUsed < aAmount) && rowFrame->HasPctBSize()) {
3216 nscoord pctBSize = rowFrame->GetInitialBSize(pctBasis);
3217 nscoord amountForRow = std::min(aAmount - amountUsed,
3218 pctBSize - rowNormalRect.BSize(wm));
3219 if (amountForRow > 0) {
3220 // XXXbz we don't need to move the row's b-position to bOriginRow?
3221 nsRect origRowRect = rowFrame->GetRect();
3222 nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
3223 rowFrame->SetSize(
3224 wm, LogicalSize(wm, rowNormalRect.ISize(wm), newRowBSize));
3225 bOriginRow += newRowBSize + cellSpacingB;
3226 bEndRG += newRowBSize + cellSpacingB;
3227 amountUsed += amountForRow;
3228 amountUsedByRG += amountForRow;
3229 // rowFrame->DidResize();
3230 nsTableFrame::RePositionViews(rowFrame);
3232 rgFrame->InvalidateFrameWithRect(origRowRect);
3233 rgFrame->InvalidateFrame();
3235 } else {
3236 if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm) &&
3237 !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
3238 rowFrame->InvalidateFrameSubtree();
3239 rowFrame->MovePositionBy(
3240 wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm)));
3241 nsTableFrame::RePositionViews(rowFrame);
3242 rowFrame->InvalidateFrameSubtree();
3244 bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB;
3245 bEndRG += rowNormalRect.BSize(wm) + cellSpacingB;
3247 rowFrame = rowFrame->GetNextRow();
3249 if (amountUsed > 0) {
3250 if (rgNormalRect.BStart(wm) != bOriginRG) {
3251 rgFrame->InvalidateFrameSubtree();
3254 nsRect origRgNormalRect = rgFrame->GetRect();
3255 nsRect origRgInkOverflow = rgFrame->InkOverflowRect();
3257 rgFrame->MovePositionBy(
3258 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
3259 rgFrame->SetSize(wm,
3260 LogicalSize(wm, rgNormalRect.ISize(wm),
3261 rgNormalRect.BSize(wm) + amountUsedByRG));
3263 nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
3264 origRgInkOverflow, false);
3266 } else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
3267 rgFrame->InvalidateFrameSubtree();
3268 rgFrame->MovePositionBy(
3269 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
3270 // Make sure child views are properly positioned
3271 nsTableFrame::RePositionViews(rgFrame);
3272 rgFrame->InvalidateFrameSubtree();
3274 bOriginRG = bEndRG;
3277 if (amountUsed >= aAmount) {
3278 ResizeCells(*this);
3279 return;
3282 // get the first row without a style bsize where its row group has an
3283 // unconstrained bsize
3284 nsTableRowGroupFrame* firstUnStyledRG = nullptr;
3285 nsTableRowFrame* firstUnStyledRow = nullptr;
3286 for (rgIdx = 0; rgIdx < rowGroups.Length() && !firstUnStyledRG; rgIdx++) {
3287 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
3288 if (!rgFrame->HasStyleBSize()) {
3289 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
3290 while (rowFrame) {
3291 if (!rowFrame->HasStyleBSize()) {
3292 firstUnStyledRG = rgFrame;
3293 firstUnStyledRow = rowFrame;
3294 break;
3296 rowFrame = rowFrame->GetNextRow();
3301 nsTableRowFrame* lastEligibleRow = nullptr;
3302 // Accumulate the correct divisor. This will be the total bsize of all
3303 // unstyled rows inside unstyled row groups, unless there are none, in which
3304 // case, it will be number of all rows. If the unstyled rows don't have a
3305 // bsize, divide the space equally among them.
3306 nscoord divisor = 0;
3307 int32_t eligibleRows = 0;
3308 bool expandEmptyRows = false;
3310 if (!firstUnStyledRow) {
3311 // there is no unstyled row
3312 divisor = GetRowCount();
3313 } else {
3314 for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
3315 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
3316 if (!firstUnStyledRG || !rgFrame->HasStyleBSize()) {
3317 nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
3318 while (rowFrame) {
3319 if (!firstUnStyledRG || !rowFrame->HasStyleBSize()) {
3320 NS_ASSERTION(rowFrame->BSize(wm) >= 0,
3321 "negative row frame block-size");
3322 divisor += rowFrame->BSize(wm);
3323 eligibleRows++;
3324 lastEligibleRow = rowFrame;
3326 rowFrame = rowFrame->GetNextRow();
3330 if (divisor <= 0) {
3331 if (eligibleRows > 0) {
3332 expandEmptyRows = true;
3333 } else {
3334 NS_ERROR("invalid divisor");
3335 return;
3339 // allocate the extra bsize to the unstyled row groups and rows
3340 nscoord bSizeToDistribute = aAmount - amountUsed;
3341 bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(-1);
3342 bEndRG = bOriginRG;
3343 for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
3344 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
3345 nscoord amountUsedByRG = 0;
3346 nscoord bOriginRow = 0;
3347 const LogicalRect rgNormalRect =
3348 rgFrame->GetLogicalNormalRect(wm, containerSize);
3349 nsRect rgInkOverflow = rgFrame->InkOverflowRect();
3350 // see if there is an eligible row group or we distribute to all rows
3351 if (!firstUnStyledRG || !rgFrame->HasStyleBSize() || !eligibleRows) {
3352 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
3353 rowFrame = rowFrame->GetNextRow()) {
3354 nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex());
3355 // We don't know the final width of the rowGroupFrame yet, so use 0,0
3356 // as a dummy containerSize here; we'll adjust the row positions at
3357 // the end, after the rowGroup size is finalized.
3358 const nsSize dummyContainerSize;
3359 const LogicalRect rowNormalRect =
3360 rowFrame->GetLogicalNormalRect(wm, dummyContainerSize);
3361 nsRect rowInkOverflow = rowFrame->InkOverflowRect();
3362 // see if there is an eligible row or we distribute to all rows
3363 if (!firstUnStyledRow || !rowFrame->HasStyleBSize() || !eligibleRows) {
3364 float ratio;
3365 if (eligibleRows) {
3366 if (!expandEmptyRows) {
3367 // The amount of additional space each row gets is proportional
3368 // to its bsize
3369 ratio = float(rowNormalRect.BSize(wm)) / float(divisor);
3370 } else {
3371 // empty rows get all the same additional space
3372 ratio = 1.0f / float(eligibleRows);
3374 } else {
3375 // all rows get the same additional space
3376 ratio = 1.0f / float(divisor);
3378 // give rows their additional space, except for the last row which
3379 // gets the remainder
3380 nscoord amountForRow =
3381 (rowFrame == lastEligibleRow)
3382 ? aAmount - amountUsed
3383 : NSToCoordRound(((float)(bSizeToDistribute)) * ratio);
3384 amountForRow = std::min(amountForRow, aAmount - amountUsed);
3386 if (bOriginRow != rowNormalRect.BStart(wm)) {
3387 rowFrame->InvalidateFrameSubtree();
3390 // update the row bsize
3391 nsRect origRowRect = rowFrame->GetRect();
3392 nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
3393 rowFrame->MovePositionBy(
3394 wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm)));
3395 rowFrame->SetSize(
3396 wm, LogicalSize(wm, rowNormalRect.ISize(wm), newRowBSize));
3398 bOriginRow += newRowBSize + cellSpacingB;
3399 bEndRG += newRowBSize + cellSpacingB;
3401 amountUsed += amountForRow;
3402 amountUsedByRG += amountForRow;
3403 NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation");
3404 // rowFrame->DidResize();
3405 nsTableFrame::RePositionViews(rowFrame);
3407 nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect,
3408 rowInkOverflow, false);
3409 } else {
3410 if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm)) {
3411 rowFrame->InvalidateFrameSubtree();
3412 rowFrame->MovePositionBy(
3413 wm, LogicalPoint(wm, 0, bOriginRow - rowNormalRect.BStart(wm)));
3414 nsTableFrame::RePositionViews(rowFrame);
3415 rowFrame->InvalidateFrameSubtree();
3417 bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB;
3418 bEndRG += rowNormalRect.BSize(wm) + cellSpacingB;
3422 if (amountUsed > 0) {
3423 if (rgNormalRect.BStart(wm) != bOriginRG) {
3424 rgFrame->InvalidateFrameSubtree();
3427 nsRect origRgNormalRect = rgFrame->GetRect();
3428 rgFrame->MovePositionBy(
3429 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
3430 rgFrame->SetSize(wm,
3431 LogicalSize(wm, rgNormalRect.ISize(wm),
3432 rgNormalRect.BSize(wm) + amountUsedByRG));
3434 nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
3435 rgInkOverflow, false);
3438 // For vertical-rl mode, we needed to position the rows relative to the
3439 // right-hand (block-start) side of the group; but we couldn't do that
3440 // above, as we didn't know the rowGroupFrame's final block size yet.
3441 // So we used a dummyContainerSize of 0,0 earlier, placing the rows to
3442 // the left of the rowGroupFrame's (physical) origin. Now we move them
3443 // all rightwards by its final width.
3444 if (wm.IsVerticalRL()) {
3445 nscoord rgWidth = rgFrame->GetSize().width;
3446 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
3447 rowFrame = rowFrame->GetNextRow()) {
3448 rowFrame->InvalidateFrameSubtree();
3449 rowFrame->MovePositionBy(nsPoint(rgWidth, 0));
3450 nsTableFrame::RePositionViews(rowFrame);
3451 rowFrame->InvalidateFrameSubtree();
3454 } else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
3455 rgFrame->InvalidateFrameSubtree();
3456 rgFrame->MovePositionBy(
3457 wm, LogicalPoint(wm, 0, bOriginRG - rgNormalRect.BStart(wm)));
3458 // Make sure child views are properly positioned
3459 nsTableFrame::RePositionViews(rgFrame);
3460 rgFrame->InvalidateFrameSubtree();
3462 bOriginRG = bEndRG;
3465 ResizeCells(*this);
3468 nscoord nsTableFrame::GetColumnISizeFromFirstInFlow(int32_t aColIndex) {
3469 MOZ_ASSERT(this == FirstInFlow());
3470 nsTableColFrame* colFrame = GetColFrame(aColIndex);
3471 return colFrame ? colFrame->GetFinalISize() : 0;
3474 nscoord nsTableFrame::GetColSpacing() {
3475 if (IsBorderCollapse()) return 0;
3477 return StyleTableBorder()->mBorderSpacingCol;
3480 // XXX: could cache this. But be sure to check style changes if you do!
3481 nscoord nsTableFrame::GetColSpacing(int32_t aColIndex) {
3482 NS_ASSERTION(aColIndex >= -1 && aColIndex <= GetColCount(),
3483 "Column index exceeds the bounds of the table");
3484 // Index is irrelevant for ordinary tables. We check that it falls within
3485 // appropriate bounds to increase confidence of correctness in situations
3486 // where it does matter.
3487 return GetColSpacing();
3490 nscoord nsTableFrame::GetColSpacing(int32_t aStartColIndex,
3491 int32_t aEndColIndex) {
3492 NS_ASSERTION(aStartColIndex >= -1 && aStartColIndex <= GetColCount(),
3493 "Start column index exceeds the bounds of the table");
3494 NS_ASSERTION(aEndColIndex >= -1 && aEndColIndex <= GetColCount(),
3495 "End column index exceeds the bounds of the table");
3496 NS_ASSERTION(aStartColIndex <= aEndColIndex,
3497 "End index must not be less than start index");
3498 // Only one possible value so just multiply it out. Tables where index
3499 // matters will override this function
3500 return GetColSpacing() * (aEndColIndex - aStartColIndex);
3503 nscoord nsTableFrame::GetRowSpacing() {
3504 if (IsBorderCollapse()) return 0;
3506 return StyleTableBorder()->mBorderSpacingRow;
3509 // XXX: could cache this. But be sure to check style changes if you do!
3510 nscoord nsTableFrame::GetRowSpacing(int32_t aRowIndex) {
3511 NS_ASSERTION(aRowIndex >= -1 && aRowIndex <= GetRowCount(),
3512 "Row index exceeds the bounds of the table");
3513 // Index is irrelevant for ordinary tables. We check that it falls within
3514 // appropriate bounds to increase confidence of correctness in situations
3515 // where it does matter.
3516 return GetRowSpacing();
3519 nscoord nsTableFrame::GetRowSpacing(int32_t aStartRowIndex,
3520 int32_t aEndRowIndex) {
3521 NS_ASSERTION(aStartRowIndex >= -1 && aStartRowIndex <= GetRowCount(),
3522 "Start row index exceeds the bounds of the table");
3523 NS_ASSERTION(aEndRowIndex >= -1 && aEndRowIndex <= GetRowCount(),
3524 "End row index exceeds the bounds of the table");
3525 NS_ASSERTION(aStartRowIndex <= aEndRowIndex,
3526 "End index must not be less than start index");
3527 // Only one possible value so just multiply it out. Tables where index
3528 // matters will override this function
3529 return GetRowSpacing() * (aEndRowIndex - aStartRowIndex);
3532 nscoord nsTableFrame::SynthesizeFallbackBaseline(
3533 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
3534 if (aBaselineGroup == BaselineSharingGroup::Last) {
3535 return 0;
3537 return BSize(aWM);
3540 /* virtual */
3541 Maybe<nscoord> nsTableFrame::GetNaturalBaselineBOffset(
3542 WritingMode aWM, BaselineSharingGroup aBaselineGroup,
3543 BaselineExportContext) const {
3544 if (StyleDisplay()->IsContainLayout()) {
3545 return Nothing{};
3548 RowGroupArray orderedRowGroups;
3549 OrderRowGroups(orderedRowGroups);
3550 // XXX not sure if this should be the size of the containing block instead.
3551 nsSize containerSize = mRect.Size();
3552 auto TableBaseline = [aWM, containerSize](
3553 nsTableRowGroupFrame* aRowGroup,
3554 nsTableRowFrame* aRow) -> Maybe<nscoord> {
3555 const nscoord rgBStart =
3556 aRowGroup->GetLogicalNormalRect(aWM, containerSize).BStart(aWM);
3557 const nscoord rowBStart =
3558 aRow->GetLogicalNormalRect(aWM, aRowGroup->GetSize()).BStart(aWM);
3559 return aRow->GetRowBaseline(aWM).map(
3560 [rgBStart, rowBStart](nscoord aBaseline) {
3561 return rgBStart + rowBStart + aBaseline;
3564 if (aBaselineGroup == BaselineSharingGroup::First) {
3565 for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
3566 nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
3567 nsTableRowFrame* row = rgFrame->GetFirstRow();
3568 if (row) {
3569 return TableBaseline(rgFrame, row);
3572 } else {
3573 for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) {
3574 nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
3575 nsTableRowFrame* row = rgFrame->GetLastRow();
3576 if (row) {
3577 return TableBaseline(rgFrame, row).map([this, aWM](nscoord aBaseline) {
3578 return BSize(aWM) - aBaseline;
3583 return Nothing{};
3586 /* ----- global methods ----- */
3588 nsTableFrame* NS_NewTableFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
3589 return new (aPresShell) nsTableFrame(aStyle, aPresShell->GetPresContext());
3592 NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame)
3594 nsTableFrame* nsTableFrame::GetTableFrame(nsIFrame* aFrame) {
3595 for (nsIFrame* ancestor = aFrame->GetParent(); ancestor;
3596 ancestor = ancestor->GetParent()) {
3597 if (ancestor->IsTableFrame()) {
3598 return static_cast<nsTableFrame*>(ancestor);
3601 MOZ_CRASH("unable to find table parent");
3602 return nullptr;
3605 bool nsTableFrame::IsAutoBSize(WritingMode aWM) {
3606 const auto& bsize = StylePosition()->BSize(aWM);
3607 if (bsize.IsAuto()) {
3608 return true;
3610 return bsize.ConvertsToPercentage() && bsize.ToPercentage() <= 0.0f;
3613 nscoord nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aReflowInput,
3614 const LogicalMargin& aBorderPadding,
3615 nscoord aIntrinsicBorderBoxBSize) {
3616 WritingMode wm = aReflowInput.GetWritingMode();
3617 nscoord bSize = aReflowInput.ComputedBSize();
3618 nscoord bp = aBorderPadding.BStartEnd(wm);
3619 if (bSize == NS_UNCONSTRAINEDSIZE) {
3620 if (aIntrinsicBorderBoxBSize == NS_UNCONSTRAINEDSIZE) {
3621 return NS_UNCONSTRAINEDSIZE;
3623 bSize = std::max(0, aIntrinsicBorderBoxBSize - bp);
3625 return aReflowInput.ApplyMinMaxBSize(bSize) + bp;
3628 bool nsTableFrame::IsAutoLayout() {
3629 if (StyleTable()->mLayoutStrategy == StyleTableLayout::Auto) return true;
3630 // a fixed-layout inline-table must have a inline size
3631 // and tables with inline size set to 'max-content' must be
3632 // auto-layout (at least as long as
3633 // FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX)
3634 const auto& iSize = StylePosition()->ISize(GetWritingMode());
3635 return iSize.IsAuto() || iSize.IsMaxContent();
3638 #ifdef DEBUG_FRAME_DUMP
3639 nsresult nsTableFrame::GetFrameName(nsAString& aResult) const {
3640 return MakeFrameName(u"Table"_ns, aResult);
3642 #endif
3644 // Find the closet sibling before aPriorChildFrame (including aPriorChildFrame)
3645 // that is of type aChildType
3646 nsIFrame* nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame,
3647 nsIFrame* aPriorChildFrame,
3648 LayoutFrameType aChildType) {
3649 nsIFrame* result = nullptr;
3650 if (!aPriorChildFrame) {
3651 return result;
3653 if (aChildType == aPriorChildFrame->Type()) {
3654 return aPriorChildFrame;
3657 // aPriorChildFrame is not of type aChildType, so we need start from
3658 // the beginnng and find the closest one
3659 nsIFrame* lastMatchingFrame = nullptr;
3660 nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild();
3661 while (childFrame && (childFrame != aPriorChildFrame)) {
3662 if (aChildType == childFrame->Type()) {
3663 lastMatchingFrame = childFrame;
3665 childFrame = childFrame->GetNextSibling();
3667 return lastMatchingFrame;
3670 #ifdef DEBUG
3671 void nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame) {
3672 if (!aKidFrame) return;
3674 for (nsIFrame* cFrame : aKidFrame->PrincipalChildList()) {
3675 nsTableRowFrame* rowFrame = do_QueryFrame(cFrame);
3676 if (rowFrame) {
3677 printf("row(%d)=%p ", rowFrame->GetRowIndex(),
3678 static_cast<void*>(rowFrame));
3679 for (nsIFrame* childFrame : cFrame->PrincipalChildList()) {
3680 nsTableCellFrame* cellFrame = do_QueryFrame(childFrame);
3681 if (cellFrame) {
3682 uint32_t colIndex = cellFrame->ColIndex();
3683 printf("cell(%u)=%p ", colIndex, static_cast<void*>(childFrame));
3686 printf("\n");
3687 } else {
3688 DumpRowGroup(rowFrame);
3693 void nsTableFrame::Dump(bool aDumpRows, bool aDumpCols, bool aDumpCellMap) {
3694 printf("***START TABLE DUMP*** \n");
3695 // dump the columns widths array
3696 printf("mColWidths=");
3697 int32_t numCols = GetColCount();
3698 int32_t colIdx;
3699 nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
3700 for (colIdx = 0; colIdx < numCols; colIdx++) {
3701 printf("%d ", fif->GetColumnISizeFromFirstInFlow(colIdx));
3703 printf("\n");
3705 if (aDumpRows) {
3706 nsIFrame* kidFrame = mFrames.FirstChild();
3707 while (kidFrame) {
3708 DumpRowGroup(kidFrame);
3709 kidFrame = kidFrame->GetNextSibling();
3713 if (aDumpCols) {
3714 // output col frame cache
3715 printf("\n col frame cache ->");
3716 for (colIdx = 0; colIdx < numCols; colIdx++) {
3717 nsTableColFrame* colFrame = mColFrames.ElementAt(colIdx);
3718 if (0 == (colIdx % 8)) {
3719 printf("\n");
3721 printf("%d=%p ", colIdx, static_cast<void*>(colFrame));
3722 nsTableColType colType = colFrame->GetColType();
3723 switch (colType) {
3724 case eColContent:
3725 printf(" content ");
3726 break;
3727 case eColAnonymousCol:
3728 printf(" anonymous-column ");
3729 break;
3730 case eColAnonymousColGroup:
3731 printf(" anonymous-colgroup ");
3732 break;
3733 case eColAnonymousCell:
3734 printf(" anonymous-cell ");
3735 break;
3738 printf("\n colgroups->");
3739 for (nsIFrame* childFrame : mColGroups) {
3740 if (LayoutFrameType::TableColGroup == childFrame->Type()) {
3741 nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)childFrame;
3742 colGroupFrame->Dump(1);
3745 for (colIdx = 0; colIdx < numCols; colIdx++) {
3746 printf("\n");
3747 nsTableColFrame* colFrame = GetColFrame(colIdx);
3748 colFrame->Dump(1);
3751 if (aDumpCellMap) {
3752 nsTableCellMap* cellMap = GetCellMap();
3753 cellMap->Dump();
3755 printf(" ***END TABLE DUMP*** \n");
3757 #endif
3759 bool nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const {
3760 if (aColIndex == 0) {
3761 return true;
3763 // Since fixed-layout tables should not have their column sizes change
3764 // as they load, we assume that all columns are significant.
3765 auto* fif = static_cast<nsTableFrame*>(FirstInFlow());
3766 if (fif->LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed) {
3767 return true;
3769 nsTableCellMap* cellMap = fif->GetCellMap();
3770 if (!cellMap) {
3771 return false;
3773 if (cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0) {
3774 return true;
3776 // Check if we have a <col> element with a non-zero definite inline size.
3777 // Note: percentages and calc(%) are intentionally not considered.
3778 if (const auto* col = fif->GetColFrame(aColIndex)) {
3779 const auto& iSize = col->StylePosition()->ISize(GetWritingMode());
3780 if (iSize.ConvertsToLength() && iSize.ToLength() > 0) {
3781 const auto& maxISize = col->StylePosition()->MaxISize(GetWritingMode());
3782 if (!maxISize.ConvertsToLength() || maxISize.ToLength() > 0) {
3783 return true;
3786 const auto& minISize = col->StylePosition()->MinISize(GetWritingMode());
3787 if (minISize.ConvertsToLength() && minISize.ToLength() > 0) {
3788 return true;
3791 return false;
3794 /********************************************************************************
3795 * Collapsing Borders
3797 * The CSS spec says to resolve border conflicts in this order:
3798 * 1) any border with the style HIDDEN wins
3799 * 2) the widest border with a style that is not NONE wins
3800 * 3) the border styles are ranked in this order, highest to lowest precedence:
3801 * double, solid, dashed, dotted, ridge, outset, groove, inset
3802 * 4) borders that are of equal width and style (differ only in color) have
3803 * this precedence: cell, row, rowgroup, col, colgroup, table
3804 * 5) if all border styles are NONE, then that's the computed border style.
3805 *******************************************************************************/
3807 #ifdef DEBUG
3808 # define VerifyNonNegativeDamageRect(r) \
3809 NS_ASSERTION((r).StartCol() >= 0, "negative col index"); \
3810 NS_ASSERTION((r).StartRow() >= 0, "negative row index"); \
3811 NS_ASSERTION((r).ColCount() >= 0, "negative cols damage"); \
3812 NS_ASSERTION((r).RowCount() >= 0, "negative rows damage");
3813 # define VerifyDamageRect(r) \
3814 VerifyNonNegativeDamageRect(r); \
3815 NS_ASSERTION((r).EndCol() <= GetColCount(), \
3816 "cols damage extends outside table"); \
3817 NS_ASSERTION((r).EndRow() <= GetRowCount(), \
3818 "rows damage extends outside table");
3819 #endif
3821 void nsTableFrame::AddBCDamageArea(const TableArea& aValue) {
3822 NS_ASSERTION(IsBorderCollapse(), "invalid AddBCDamageArea call");
3823 #ifdef DEBUG
3824 VerifyDamageRect(aValue);
3825 #endif
3827 SetNeedToCalcBCBorders(true);
3828 SetNeedToCalcHasBCBorders(true);
3829 // Get the property
3830 BCPropertyData* value = GetOrCreateBCProperty();
3831 if (value) {
3832 #ifdef DEBUG
3833 VerifyNonNegativeDamageRect(value->mDamageArea);
3834 #endif
3835 // Clamp the old damage area to the current table area in case it shrunk.
3836 int32_t cols = GetColCount();
3837 if (value->mDamageArea.EndCol() > cols) {
3838 if (value->mDamageArea.StartCol() > cols) {
3839 value->mDamageArea.StartCol() = cols;
3840 value->mDamageArea.ColCount() = 0;
3841 } else {
3842 value->mDamageArea.ColCount() = cols - value->mDamageArea.StartCol();
3845 int32_t rows = GetRowCount();
3846 if (value->mDamageArea.EndRow() > rows) {
3847 if (value->mDamageArea.StartRow() > rows) {
3848 value->mDamageArea.StartRow() = rows;
3849 value->mDamageArea.RowCount() = 0;
3850 } else {
3851 value->mDamageArea.RowCount() = rows - value->mDamageArea.StartRow();
3855 // Construct a union of the new and old damage areas.
3856 value->mDamageArea.UnionArea(value->mDamageArea, aValue);
3860 void nsTableFrame::SetFullBCDamageArea() {
3861 NS_ASSERTION(IsBorderCollapse(), "invalid SetFullBCDamageArea call");
3863 SetNeedToCalcBCBorders(true);
3864 SetNeedToCalcHasBCBorders(true);
3866 BCPropertyData* value = GetOrCreateBCProperty();
3867 if (value) {
3868 value->mDamageArea = TableArea(0, 0, GetColCount(), GetRowCount());
3872 /* BCCellBorder represents a border segment which can be either an inline-dir
3873 * or a block-dir segment. For each segment we need to know the color, width,
3874 * style, who owns it and how long it is in cellmap coordinates.
3875 * Ownership of these segments is important to calculate which corners should
3876 * be bevelled. This structure has dual use, its used first to compute the
3877 * dominant border for inline-dir and block-dir segments and to store the
3878 * preliminary computed border results in the BCCellBorders structure.
3879 * This temporary storage is not symmetric with respect to inline-dir and
3880 * block-dir border segments, its always column oriented. For each column in
3881 * the cellmap there is a temporary stored block-dir and inline-dir segment.
3882 * XXX_Bernd this asymmetry is the root of those rowspan bc border errors
3884 struct BCCellBorder {
3885 BCCellBorder() { Reset(0, 1); }
3886 void Reset(uint32_t aRowIndex, uint32_t aRowSpan);
3887 nscolor color; // border segment color
3888 BCPixelSize width; // border segment width in pixel coordinates !!
3889 StyleBorderStyle style; // border segment style, possible values are defined
3890 // in nsStyleConsts.h as StyleBorderStyle::*
3891 BCBorderOwner owner; // border segment owner, possible values are defined
3892 // in celldata.h. In the cellmap for each border
3893 // segment we store the owner and later when
3894 // painting we know the owner and can retrieve the
3895 // style info from the corresponding frame
3896 int32_t rowIndex; // rowIndex of temporary stored inline-dir border
3897 // segments relative to the table
3898 int32_t rowSpan; // row span of temporary stored inline-dir border
3899 // segments
3902 void BCCellBorder::Reset(uint32_t aRowIndex, uint32_t aRowSpan) {
3903 style = StyleBorderStyle::None;
3904 color = 0;
3905 width = 0;
3906 owner = eTableOwner;
3907 rowIndex = aRowIndex;
3908 rowSpan = aRowSpan;
3911 class BCMapCellIterator;
3913 /*****************************************************************
3914 * BCMapCellInfo
3915 * This structure stores information about the cellmap and all involved
3916 * table related frames that are used during the computation of winning borders
3917 * in CalcBCBorders so that they do need to be looked up again and again when
3918 * iterating over the cells.
3919 ****************************************************************/
3920 struct BCMapCellInfo {
3921 explicit BCMapCellInfo(nsTableFrame* aTableFrame);
3922 void ResetCellInfo();
3923 void SetInfo(nsTableRowFrame* aNewRow, int32_t aColIndex,
3924 BCCellData* aCellData, BCMapCellIterator* aIter,
3925 nsCellMap* aCellMap = nullptr);
3926 // The BCMapCellInfo has functions to set the continous
3927 // border widths (see nsTablePainter.cpp for a description of the continous
3928 // borders concept). The widths are computed inside these functions based on
3929 // the current position inside the table and the cached frames that correspond
3930 // to this position. The widths are stored in member variables of the internal
3931 // table frames.
3932 void SetTableBStartIStartContBCBorder();
3933 void SetRowGroupIStartContBCBorder();
3934 void SetRowGroupIEndContBCBorder();
3935 void SetRowGroupBEndContBCBorder();
3936 void SetRowIStartContBCBorder();
3937 void SetRowIEndContBCBorder();
3938 void SetColumnBStartIEndContBCBorder();
3939 void SetColumnBEndContBCBorder();
3940 void SetColGroupBEndContBCBorder();
3941 void SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup,
3942 nsTableRowFrame* aNextRow);
3944 // functions to set the border widths on the table related frames, where the
3945 // knowledge about the current position in the table is used.
3946 void SetTableBStartBorderWidth(BCPixelSize aWidth);
3947 void SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth);
3948 void SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth);
3949 void SetTableBEndBorderWidth(BCPixelSize aWidth);
3950 void SetIStartBorderWidths(BCPixelSize aWidth);
3951 void SetIEndBorderWidths(BCPixelSize aWidth);
3952 void SetBStartBorderWidths(BCPixelSize aWidth);
3953 void SetBEndBorderWidths(BCPixelSize aWidth);
3955 // functions to compute the borders; they depend on the
3956 // knowledge about the current position in the table. The edge functions
3957 // should be called if a table edge is involved, otherwise the internal
3958 // functions should be called.
3959 BCCellBorder GetBStartEdgeBorder();
3960 BCCellBorder GetBEndEdgeBorder();
3961 BCCellBorder GetIStartEdgeBorder();
3962 BCCellBorder GetIEndEdgeBorder();
3963 BCCellBorder GetIEndInternalBorder();
3964 BCCellBorder GetIStartInternalBorder();
3965 BCCellBorder GetBStartInternalBorder();
3966 BCCellBorder GetBEndInternalBorder();
3968 // functions to set the internal position information
3969 void SetColumn(int32_t aColX);
3970 // Increment the row as we loop over the rows of a rowspan
3971 void IncrementRow(bool aResetToBStartRowOfCell = false);
3973 // Helper functions to get extent of the cell
3974 int32_t GetCellEndRowIndex() const;
3975 int32_t GetCellEndColIndex() const;
3977 // storage of table information
3978 nsTableFrame* mTableFrame;
3979 nsTableFrame* mTableFirstInFlow;
3980 int32_t mNumTableRows;
3981 int32_t mNumTableCols;
3982 BCPropertyData* mTableBCData;
3983 WritingMode mTableWM;
3985 // a cell can only belong to one rowgroup
3986 nsTableRowGroupFrame* mRowGroup;
3988 // a cell with a rowspan has a bstart and a bend row, and rows in between
3989 nsTableRowFrame* mStartRow;
3990 nsTableRowFrame* mEndRow;
3991 nsTableRowFrame* mCurrentRowFrame;
3993 // a cell with a colspan has an istart and iend column and columns in between
3994 // they can belong to different colgroups
3995 nsTableColGroupFrame* mColGroup;
3996 nsTableColGroupFrame* mCurrentColGroupFrame;
3998 nsTableColFrame* mStartCol;
3999 nsTableColFrame* mEndCol;
4000 nsTableColFrame* mCurrentColFrame;
4002 // cell information
4003 BCCellData* mCellData;
4004 nsBCTableCellFrame* mCell;
4006 int32_t mRowIndex;
4007 int32_t mRowSpan;
4008 int32_t mColIndex;
4009 int32_t mColSpan;
4011 // flags to describe the position of the cell with respect to the row- and
4012 // colgroups, for instance mRgAtStart documents that the bStart cell border
4013 // hits a rowgroup border
4014 bool mRgAtStart;
4015 bool mRgAtEnd;
4016 bool mCgAtStart;
4017 bool mCgAtEnd;
4020 BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame)
4021 : mTableFrame(aTableFrame),
4022 mTableFirstInFlow(static_cast<nsTableFrame*>(aTableFrame->FirstInFlow())),
4023 mNumTableRows(aTableFrame->GetRowCount()),
4024 mNumTableCols(aTableFrame->GetColCount()),
4025 mTableBCData(mTableFrame->GetProperty(TableBCProperty())),
4026 mTableWM(aTableFrame->Style()),
4027 mCurrentRowFrame(nullptr),
4028 mCurrentColGroupFrame(nullptr),
4029 mCurrentColFrame(nullptr) {
4030 ResetCellInfo();
4033 void BCMapCellInfo::ResetCellInfo() {
4034 mCellData = nullptr;
4035 mRowGroup = nullptr;
4036 mStartRow = nullptr;
4037 mEndRow = nullptr;
4038 mColGroup = nullptr;
4039 mStartCol = nullptr;
4040 mEndCol = nullptr;
4041 mCell = nullptr;
4042 mRowIndex = mRowSpan = mColIndex = mColSpan = 0;
4043 mRgAtStart = mRgAtEnd = mCgAtStart = mCgAtEnd = false;
4046 inline int32_t BCMapCellInfo::GetCellEndRowIndex() const {
4047 return mRowIndex + mRowSpan - 1;
4050 inline int32_t BCMapCellInfo::GetCellEndColIndex() const {
4051 return mColIndex + mColSpan - 1;
4054 class BCMapCellIterator {
4055 public:
4056 BCMapCellIterator(nsTableFrame* aTableFrame, const TableArea& aDamageArea);
4058 void First(BCMapCellInfo& aMapCellInfo);
4060 void Next(BCMapCellInfo& aMapCellInfo);
4062 void PeekIEnd(BCMapCellInfo& aRefInfo, uint32_t aRowIndex,
4063 BCMapCellInfo& aAjaInfo);
4065 void PeekBEnd(BCMapCellInfo& aRefInfo, uint32_t aColIndex,
4066 BCMapCellInfo& aAjaInfo);
4068 bool IsNewRow() { return mIsNewRow; }
4070 nsTableRowFrame* GetPrevRow() const { return mPrevRow; }
4071 nsTableRowFrame* GetCurrentRow() const { return mRow; }
4072 nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup; }
4074 int32_t mRowGroupStart;
4075 int32_t mRowGroupEnd;
4076 bool mAtEnd;
4077 nsCellMap* mCellMap;
4079 private:
4080 bool SetNewRow(nsTableRowFrame* row = nullptr);
4081 bool SetNewRowGroup(bool aFindFirstDamagedRow);
4083 nsTableFrame* mTableFrame;
4084 nsTableCellMap* mTableCellMap;
4085 nsTableFrame::RowGroupArray mRowGroups;
4086 nsTableRowGroupFrame* mRowGroup;
4087 int32_t mRowGroupIndex;
4088 uint32_t mNumTableRows;
4089 nsTableRowFrame* mRow;
4090 nsTableRowFrame* mPrevRow;
4091 bool mIsNewRow;
4092 int32_t mRowIndex;
4093 uint32_t mNumTableCols;
4094 int32_t mColIndex;
4095 nsPoint mAreaStart; // These are not really points in the usual
4096 nsPoint mAreaEnd; // sense; they're column/row coordinates
4097 // in the cell map.
4100 BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame,
4101 const TableArea& aDamageArea)
4102 : mRowGroupStart(0),
4103 mRowGroupEnd(0),
4104 mCellMap(nullptr),
4105 mTableFrame(aTableFrame),
4106 mRowGroup(nullptr),
4107 mPrevRow(nullptr),
4108 mIsNewRow(false) {
4109 mTableCellMap = aTableFrame->GetCellMap();
4111 mAreaStart.x = aDamageArea.StartCol();
4112 mAreaStart.y = aDamageArea.StartRow();
4113 mAreaEnd.x = aDamageArea.EndCol() - 1;
4114 mAreaEnd.y = aDamageArea.EndRow() - 1;
4116 mNumTableRows = mTableFrame->GetRowCount();
4117 mRow = nullptr;
4118 mRowIndex = 0;
4119 mNumTableCols = mTableFrame->GetColCount();
4120 mColIndex = 0;
4121 mRowGroupIndex = -1;
4123 // Get the ordered row groups
4124 aTableFrame->OrderRowGroups(mRowGroups);
4126 mAtEnd = true; // gets reset when First() is called
4129 // fill fields that we need for border collapse computation on a given cell
4130 void BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow, int32_t aColIndex,
4131 BCCellData* aCellData, BCMapCellIterator* aIter,
4132 nsCellMap* aCellMap) {
4133 // fill the cell information
4134 mCellData = aCellData;
4135 mColIndex = aColIndex;
4137 // initialize the row information if it was not previously set for cells in
4138 // this row
4139 mRowIndex = 0;
4140 if (aNewRow) {
4141 mStartRow = aNewRow;
4142 mRowIndex = aNewRow->GetRowIndex();
4145 // fill cell frame info and row information
4146 mCell = nullptr;
4147 mRowSpan = 1;
4148 mColSpan = 1;
4149 if (aCellData) {
4150 mCell = static_cast<nsBCTableCellFrame*>(aCellData->GetCellFrame());
4151 if (mCell) {
4152 if (!mStartRow) {
4153 mStartRow = mCell->GetTableRowFrame();
4154 if (!mStartRow) ABORT0();
4155 mRowIndex = mStartRow->GetRowIndex();
4157 mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap);
4158 mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap);
4162 if (!mStartRow) {
4163 mStartRow = aIter->GetCurrentRow();
4165 if (1 == mRowSpan) {
4166 mEndRow = mStartRow;
4167 } else {
4168 mEndRow = mStartRow->GetNextRow();
4169 if (mEndRow) {
4170 for (int32_t span = 2; mEndRow && span < mRowSpan; span++) {
4171 mEndRow = mEndRow->GetNextRow();
4173 NS_ASSERTION(mEndRow, "spanned row not found");
4174 } else {
4175 NS_ERROR("error in cell map");
4176 mRowSpan = 1;
4177 mEndRow = mStartRow;
4180 // row group frame info
4181 // try to reuse the rgStart and rgEnd from the iterator as calls to
4182 // GetRowCount() are computationally expensive and should be avoided if
4183 // possible
4184 uint32_t rgStart = aIter->mRowGroupStart;
4185 uint32_t rgEnd = aIter->mRowGroupEnd;
4186 mRowGroup = mStartRow->GetTableRowGroupFrame();
4187 if (mRowGroup != aIter->GetCurrentRowGroup()) {
4188 rgStart = mRowGroup->GetStartRowIndex();
4189 rgEnd = rgStart + mRowGroup->GetRowCount() - 1;
4191 uint32_t rowIndex = mStartRow->GetRowIndex();
4192 mRgAtStart = rgStart == rowIndex;
4193 mRgAtEnd = rgEnd == rowIndex + mRowSpan - 1;
4195 // col frame info
4196 mStartCol = mTableFirstInFlow->GetColFrame(aColIndex);
4197 if (!mStartCol) ABORT0();
4199 mEndCol = mStartCol;
4200 if (mColSpan > 1) {
4201 nsTableColFrame* colFrame =
4202 mTableFirstInFlow->GetColFrame(aColIndex + mColSpan - 1);
4203 if (!colFrame) ABORT0();
4204 mEndCol = colFrame;
4207 // col group frame info
4208 mColGroup = mStartCol->GetTableColGroupFrame();
4209 int32_t cgStart = mColGroup->GetStartColumnIndex();
4210 int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1);
4211 mCgAtStart = cgStart == aColIndex;
4212 mCgAtEnd = cgEnd == aColIndex + mColSpan - 1;
4215 bool BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow) {
4216 mAtEnd = true;
4217 mPrevRow = mRow;
4218 if (aRow) {
4219 mRow = aRow;
4220 } else if (mRow) {
4221 mRow = mRow->GetNextRow();
4223 if (mRow) {
4224 mRowIndex = mRow->GetRowIndex();
4225 // get to the first entry with an originating cell
4226 int32_t rgRowIndex = mRowIndex - mRowGroupStart;
4227 if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length()) ABORT1(false);
4228 const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex];
4230 for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) {
4231 CellData* cellData = row.SafeElementAt(mColIndex);
4232 if (!cellData) { // add a dead cell data
4233 TableArea damageArea;
4234 cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex,
4235 false, 0, damageArea);
4236 if (!cellData) ABORT1(false);
4238 if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
4239 break;
4242 mIsNewRow = true;
4243 mAtEnd = false;
4244 } else
4245 ABORT1(false);
4247 return !mAtEnd;
4250 bool BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow) {
4251 mAtEnd = true;
4252 int32_t numRowGroups = mRowGroups.Length();
4253 mCellMap = nullptr;
4254 for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) {
4255 mRowGroup = mRowGroups[mRowGroupIndex];
4256 int32_t rowCount = mRowGroup->GetRowCount();
4257 mRowGroupStart = mRowGroup->GetStartRowIndex();
4258 mRowGroupEnd = mRowGroupStart + rowCount - 1;
4259 if (rowCount > 0) {
4260 mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap);
4261 if (!mCellMap) ABORT1(false);
4262 nsTableRowFrame* firstRow = mRowGroup->GetFirstRow();
4263 if (aFindFirstDamagedRow) {
4264 if ((mAreaStart.y >= mRowGroupStart) &&
4265 (mAreaStart.y <= mRowGroupEnd)) {
4266 // the damage area starts in the row group
4268 // find the correct first damaged row
4269 int32_t numRows = mAreaStart.y - mRowGroupStart;
4270 for (int32_t i = 0; i < numRows; i++) {
4271 firstRow = firstRow->GetNextRow();
4272 if (!firstRow) ABORT1(false);
4275 } else {
4276 continue;
4279 if (SetNewRow(firstRow)) { // sets mAtEnd
4280 break;
4285 return !mAtEnd;
4288 void BCMapCellIterator::First(BCMapCellInfo& aMapInfo) {
4289 aMapInfo.ResetCellInfo();
4291 SetNewRowGroup(true); // sets mAtEnd
4292 while (!mAtEnd) {
4293 if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) {
4294 BCCellData* cellData = static_cast<BCCellData*>(
4295 mCellMap->GetDataAt(mAreaStart.y - mRowGroupStart, mAreaStart.x));
4296 if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
4297 aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this);
4298 return;
4299 } else {
4300 NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)),
4301 "damage area expanded incorrectly");
4304 SetNewRowGroup(true); // sets mAtEnd
4308 void BCMapCellIterator::Next(BCMapCellInfo& aMapInfo) {
4309 if (mAtEnd) ABORT0();
4310 aMapInfo.ResetCellInfo();
4312 mIsNewRow = false;
4313 mColIndex++;
4314 while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) {
4315 for (; mColIndex <= mAreaEnd.x; mColIndex++) {
4316 int32_t rgRowIndex = mRowIndex - mRowGroupStart;
4317 BCCellData* cellData =
4318 static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, mColIndex));
4319 if (!cellData) { // add a dead cell data
4320 TableArea damageArea;
4321 cellData = static_cast<BCCellData*>(mCellMap->AppendCell(
4322 *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
4323 if (!cellData) ABORT0();
4325 if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
4326 aMapInfo.SetInfo(mRow, mColIndex, cellData, this);
4327 return;
4330 if (mRowIndex >= mRowGroupEnd) {
4331 SetNewRowGroup(false); // could set mAtEnd
4332 } else {
4333 SetNewRow(); // could set mAtEnd
4336 mAtEnd = true;
4339 void BCMapCellIterator::PeekIEnd(BCMapCellInfo& aRefInfo, uint32_t aRowIndex,
4340 BCMapCellInfo& aAjaInfo) {
4341 aAjaInfo.ResetCellInfo();
4342 int32_t colIndex = aRefInfo.mColIndex + aRefInfo.mColSpan;
4343 uint32_t rgRowIndex = aRowIndex - mRowGroupStart;
4345 BCCellData* cellData =
4346 static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, colIndex));
4347 if (!cellData) { // add a dead cell data
4348 NS_ASSERTION(colIndex < mTableCellMap->GetColCount(), "program error");
4349 TableArea damageArea;
4350 cellData = static_cast<BCCellData*>(mCellMap->AppendCell(
4351 *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
4352 if (!cellData) ABORT0();
4354 nsTableRowFrame* row = nullptr;
4355 if (cellData->IsRowSpan()) {
4356 rgRowIndex -= cellData->GetRowSpanOffset();
4357 cellData =
4358 static_cast<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, colIndex));
4359 if (!cellData) ABORT0();
4360 } else {
4361 row = mRow;
4363 aAjaInfo.SetInfo(row, colIndex, cellData, this);
4366 void BCMapCellIterator::PeekBEnd(BCMapCellInfo& aRefInfo, uint32_t aColIndex,
4367 BCMapCellInfo& aAjaInfo) {
4368 aAjaInfo.ResetCellInfo();
4369 int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan;
4370 int32_t rgRowIndex = rowIndex - mRowGroupStart;
4371 nsTableRowGroupFrame* rg = mRowGroup;
4372 nsCellMap* cellMap = mCellMap;
4373 nsTableRowFrame* nextRow = nullptr;
4374 if (rowIndex > mRowGroupEnd) {
4375 int32_t nextRgIndex = mRowGroupIndex;
4376 do {
4377 nextRgIndex++;
4378 rg = mRowGroups.SafeElementAt(nextRgIndex);
4379 if (rg) {
4380 cellMap = mTableCellMap->GetMapFor(rg, cellMap);
4381 if (!cellMap) ABORT0();
4382 rgRowIndex = 0;
4383 nextRow = rg->GetFirstRow();
4385 } while (rg && !nextRow);
4386 if (!rg) return;
4387 } else {
4388 // get the row within the same row group
4389 nextRow = mRow;
4390 for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) {
4391 nextRow = nextRow->GetNextRow();
4392 if (!nextRow) ABORT0();
4396 BCCellData* cellData =
4397 static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
4398 if (!cellData) { // add a dead cell data
4399 NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error");
4400 TableArea damageArea;
4401 cellData = static_cast<BCCellData*>(cellMap->AppendCell(
4402 *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
4403 if (!cellData) ABORT0();
4405 if (cellData->IsColSpan()) {
4406 aColIndex -= cellData->GetColSpanOffset();
4407 cellData =
4408 static_cast<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
4410 aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap);
4413 #define CELL_CORNER true
4415 /** return the border style, border color and optionally the width in
4416 * pixel for a given frame and side
4417 * @param aFrame - query the info for this frame
4418 * @param aTableWM - the writing-mode of the frame
4419 * @param aSide - the side of the frame
4420 * @param aStyle - the border style
4421 * @param aColor - the border color
4422 * @param aWidth - the border width in px
4424 static void GetColorAndStyle(const nsIFrame* aFrame, WritingMode aTableWM,
4425 LogicalSide aSide, StyleBorderStyle* aStyle,
4426 nscolor* aColor, BCPixelSize* aWidth = nullptr) {
4427 MOZ_ASSERT(aFrame, "null frame");
4428 MOZ_ASSERT(aStyle && aColor, "null argument");
4430 // initialize out arg
4431 *aColor = 0;
4432 if (aWidth) {
4433 *aWidth = 0;
4436 const nsStyleBorder* styleData = aFrame->StyleBorder();
4437 mozilla::Side physicalSide = aTableWM.PhysicalSide(aSide);
4438 *aStyle = styleData->GetBorderStyle(physicalSide);
4440 if ((StyleBorderStyle::None == *aStyle) ||
4441 (StyleBorderStyle::Hidden == *aStyle)) {
4442 return;
4444 *aColor = aFrame->Style()->GetVisitedDependentColor(
4445 nsStyleBorder::BorderColorFieldFor(physicalSide));
4447 if (aWidth) {
4448 nscoord width = styleData->GetComputedBorderWidth(physicalSide);
4449 *aWidth = aFrame->PresContext()->AppUnitsToDevPixels(width);
4453 /** coerce the paint style as required by CSS2.1
4454 * @param aFrame - query the info for this frame
4455 * @param aTableWM - the writing mode of the frame
4456 * @param aSide - the side of the frame
4457 * @param aStyle - the border style
4458 * @param aColor - the border color
4460 static void GetPaintStyleInfo(const nsIFrame* aFrame, WritingMode aTableWM,
4461 LogicalSide aSide, StyleBorderStyle* aStyle,
4462 nscolor* aColor) {
4463 GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor);
4464 if (StyleBorderStyle::Inset == *aStyle) {
4465 *aStyle = StyleBorderStyle::Ridge;
4466 } else if (StyleBorderStyle::Outset == *aStyle) {
4467 *aStyle = StyleBorderStyle::Groove;
4471 class nsDelayedCalcBCBorders : public Runnable {
4472 public:
4473 explicit nsDelayedCalcBCBorders(nsIFrame* aFrame)
4474 : mozilla::Runnable("nsDelayedCalcBCBorders"), mFrame(aFrame) {}
4476 NS_IMETHOD Run() override {
4477 if (mFrame) {
4478 nsTableFrame* tableFrame = static_cast<nsTableFrame*>(mFrame.GetFrame());
4479 if (tableFrame->NeedToCalcBCBorders()) {
4480 tableFrame->CalcBCBorders();
4483 return NS_OK;
4486 private:
4487 WeakFrame mFrame;
4490 bool nsTableFrame::BCRecalcNeeded(ComputedStyle* aOldComputedStyle,
4491 ComputedStyle* aNewComputedStyle) {
4492 // Attention: the old ComputedStyle is the one we're forgetting,
4493 // and hence possibly completely bogus for GetStyle* purposes.
4494 // We use PeekStyleData instead.
4496 const nsStyleBorder* oldStyleData = aOldComputedStyle->StyleBorder();
4497 const nsStyleBorder* newStyleData = aNewComputedStyle->StyleBorder();
4498 nsChangeHint change = newStyleData->CalcDifference(*oldStyleData);
4499 if (!change) return false;
4500 if (change & nsChangeHint_NeedReflow)
4501 return true; // the caller only needs to mark the bc damage area
4502 if (change & nsChangeHint_RepaintFrame) {
4503 // we need to recompute the borders and the caller needs to mark
4504 // the bc damage area
4505 // XXX In principle this should only be necessary for border style changes
4506 // However the bc painting code tries to maximize the drawn border segments
4507 // so it stores in the cellmap where a new border segment starts and this
4508 // introduces a unwanted cellmap data dependence on color
4509 nsCOMPtr<nsIRunnable> evt = new nsDelayedCalcBCBorders(this);
4510 nsresult rv =
4511 GetContent()->OwnerDoc()->Dispatch(TaskCategory::Other, evt.forget());
4512 return NS_SUCCEEDED(rv);
4514 return false;
4517 // Compare two border segments, this comparison depends whether the two
4518 // segments meet at a corner and whether the second segment is inline-dir.
4519 // The return value is whichever of aBorder1 or aBorder2 dominates.
4520 static const BCCellBorder& CompareBorders(
4521 bool aIsCorner, // Pass true for corner calculations
4522 const BCCellBorder& aBorder1, const BCCellBorder& aBorder2,
4523 bool aSecondIsInlineDir, bool* aFirstDominates = nullptr) {
4524 bool firstDominates = true;
4526 if (StyleBorderStyle::Hidden == aBorder1.style) {
4527 firstDominates = !aIsCorner;
4528 } else if (StyleBorderStyle::Hidden == aBorder2.style) {
4529 firstDominates = aIsCorner;
4530 } else if (aBorder1.width < aBorder2.width) {
4531 firstDominates = false;
4532 } else if (aBorder1.width == aBorder2.width) {
4533 if (static_cast<uint8_t>(aBorder1.style) <
4534 static_cast<uint8_t>(aBorder2.style)) {
4535 firstDominates = false;
4536 } else if (aBorder1.style == aBorder2.style) {
4537 if (aBorder1.owner == aBorder2.owner) {
4538 firstDominates = !aSecondIsInlineDir;
4539 } else if (aBorder1.owner < aBorder2.owner) {
4540 firstDominates = false;
4545 if (aFirstDominates) *aFirstDominates = firstDominates;
4547 if (firstDominates) return aBorder1;
4548 return aBorder2;
4551 /** calc the dominant border by considering the table, row/col group, row/col,
4552 * cell.
4553 * Depending on whether the side is block-dir or inline-dir and whether
4554 * adjacent frames are taken into account the ownership of a single border
4555 * segment is defined. The return value is the dominating border
4556 * The cellmap stores only bstart and istart borders for each cellmap position.
4557 * If the cell border is owned by the cell that is istart-wards of the border
4558 * it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other
4559 * scenarios with a adjacent owner.
4560 * @param xxxFrame - the frame for style information, might be zero if
4561 * it should not be considered
4562 * @param aTableWM - the writing mode of the frame
4563 * @param aSide - side of the frames that should be considered
4564 * @param aAja - the border comparison takes place from the point of
4565 * a frame that is adjacent to the cellmap entry, for
4566 * when a cell owns its lower border it will be the
4567 * adjacent owner as in the cellmap only bstart and
4568 * istart borders are stored.
4570 static BCCellBorder CompareBorders(
4571 const nsIFrame* aTableFrame, const nsIFrame* aColGroupFrame,
4572 const nsIFrame* aColFrame, const nsIFrame* aRowGroupFrame,
4573 const nsIFrame* aRowFrame, const nsIFrame* aCellFrame, WritingMode aTableWM,
4574 LogicalSide aSide, bool aAja) {
4575 BCCellBorder border, tempBorder;
4576 bool inlineAxis = IsBlock(aSide);
4578 // start with the table as dominant if present
4579 if (aTableFrame) {
4580 GetColorAndStyle(aTableFrame, aTableWM, aSide, &border.style, &border.color,
4581 &border.width);
4582 border.owner = eTableOwner;
4583 if (StyleBorderStyle::Hidden == border.style) {
4584 return border;
4587 // see if the colgroup is dominant
4588 if (aColGroupFrame) {
4589 GetColorAndStyle(aColGroupFrame, aTableWM, aSide, &tempBorder.style,
4590 &tempBorder.color, &tempBorder.width);
4591 tempBorder.owner = aAja && !inlineAxis ? eAjaColGroupOwner : eColGroupOwner;
4592 // pass here and below false for aSecondIsInlineDir as it is only used for
4593 // corner calculations.
4594 border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
4595 if (StyleBorderStyle::Hidden == border.style) {
4596 return border;
4599 // see if the col is dominant
4600 if (aColFrame) {
4601 GetColorAndStyle(aColFrame, aTableWM, aSide, &tempBorder.style,
4602 &tempBorder.color, &tempBorder.width);
4603 tempBorder.owner = aAja && !inlineAxis ? eAjaColOwner : eColOwner;
4604 border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
4605 if (StyleBorderStyle::Hidden == border.style) {
4606 return border;
4609 // see if the rowgroup is dominant
4610 if (aRowGroupFrame) {
4611 GetColorAndStyle(aRowGroupFrame, aTableWM, aSide, &tempBorder.style,
4612 &tempBorder.color, &tempBorder.width);
4613 tempBorder.owner = aAja && inlineAxis ? eAjaRowGroupOwner : eRowGroupOwner;
4614 border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
4615 if (StyleBorderStyle::Hidden == border.style) {
4616 return border;
4619 // see if the row is dominant
4620 if (aRowFrame) {
4621 GetColorAndStyle(aRowFrame, aTableWM, aSide, &tempBorder.style,
4622 &tempBorder.color, &tempBorder.width);
4623 tempBorder.owner = aAja && inlineAxis ? eAjaRowOwner : eRowOwner;
4624 border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
4625 if (StyleBorderStyle::Hidden == border.style) {
4626 return border;
4629 // see if the cell is dominant
4630 if (aCellFrame) {
4631 GetColorAndStyle(aCellFrame, aTableWM, aSide, &tempBorder.style,
4632 &tempBorder.color, &tempBorder.width);
4633 tempBorder.owner = aAja ? eAjaCellOwner : eCellOwner;
4634 border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
4636 return border;
4639 static bool Perpendicular(mozilla::LogicalSide aSide1,
4640 mozilla::LogicalSide aSide2) {
4641 return IsInline(aSide1) != IsInline(aSide2);
4644 // Initial value indicating that BCCornerInfo's ownerStyle hasn't been set yet.
4645 #define BORDER_STYLE_UNSET static_cast<StyleBorderStyle>(255)
4647 // XXX allocate this as number-of-cols+1 instead of number-of-cols+1 *
4648 // number-of-rows+1
4649 struct BCCornerInfo {
4650 BCCornerInfo() {
4651 ownerColor = 0;
4652 ownerWidth = subWidth = ownerElem = subSide = subElem = hasDashDot =
4653 numSegs = bevel = 0;
4654 ownerSide = eLogicalSideBStart;
4655 ownerStyle = BORDER_STYLE_UNSET;
4656 subStyle = StyleBorderStyle::Solid;
4659 void Set(mozilla::LogicalSide aSide, BCCellBorder border);
4661 void Update(mozilla::LogicalSide aSide, BCCellBorder border);
4663 nscolor ownerColor; // color of borderOwner
4664 uint16_t ownerWidth; // pixel width of borderOwner
4665 uint16_t subWidth; // pixel width of the largest border intersecting the
4666 // border perpendicular to ownerSide
4667 StyleBorderStyle subStyle; // border style of subElem
4668 StyleBorderStyle ownerStyle; // border style of ownerElem
4669 uint16_t ownerSide : 2; // LogicalSide (e.g eLogicalSideBStart, etc) of the
4670 // border owning the corner relative to the corner
4671 uint16_t
4672 ownerElem : 4; // elem type (e.g. eTable, eGroup, etc) owning the corner
4673 uint16_t subSide : 2; // side of border with subWidth relative to the corner
4674 uint16_t subElem : 4; // elem type (e.g. eTable, eGroup, etc) of sub owner
4675 uint16_t hasDashDot : 1; // does a dashed, dotted segment enter the corner,
4676 // they cannot be beveled
4677 uint16_t numSegs : 3; // number of segments entering corner
4678 uint16_t bevel : 1; // is the corner beveled (uses the above two fields
4679 // together with subWidth)
4680 // 7 bits are unused
4683 void BCCornerInfo::Set(mozilla::LogicalSide aSide, BCCellBorder aBorder) {
4684 // FIXME bug 1508921: We mask 4-bit BCBorderOwner enum to 3 bits to preserve
4685 // buggy behavior found by the frame_above_rules_all.html mochitest.
4686 ownerElem = aBorder.owner & 0x7;
4688 ownerStyle = aBorder.style;
4689 ownerWidth = aBorder.width;
4690 ownerColor = aBorder.color;
4691 ownerSide = aSide;
4692 hasDashDot = 0;
4693 numSegs = 0;
4694 if (aBorder.width > 0) {
4695 numSegs++;
4696 hasDashDot = (StyleBorderStyle::Dashed == aBorder.style) ||
4697 (StyleBorderStyle::Dotted == aBorder.style);
4699 bevel = 0;
4700 subWidth = 0;
4701 // the following will get set later
4702 subSide = IsInline(aSide) ? eLogicalSideBStart : eLogicalSideIStart;
4703 subElem = eTableOwner;
4704 subStyle = StyleBorderStyle::Solid;
4707 void BCCornerInfo::Update(mozilla::LogicalSide aSide, BCCellBorder aBorder) {
4708 if (ownerStyle == BORDER_STYLE_UNSET) {
4709 Set(aSide, aBorder);
4710 } else {
4711 bool isInline = IsInline(aSide); // relative to the corner
4712 BCCellBorder oldBorder, tempBorder;
4713 oldBorder.owner = (BCBorderOwner)ownerElem;
4714 oldBorder.style = ownerStyle;
4715 oldBorder.width = ownerWidth;
4716 oldBorder.color = ownerColor;
4718 LogicalSide oldSide = LogicalSide(ownerSide);
4720 bool existingWins = false;
4721 tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline,
4722 &existingWins);
4724 ownerElem = tempBorder.owner;
4725 ownerStyle = tempBorder.style;
4726 ownerWidth = tempBorder.width;
4727 ownerColor = tempBorder.color;
4728 if (existingWins) { // existing corner is dominant
4729 if (::Perpendicular(LogicalSide(ownerSide), aSide)) {
4730 // see if the new sub info replaces the old
4731 BCCellBorder subBorder;
4732 subBorder.owner = (BCBorderOwner)subElem;
4733 subBorder.style = subStyle;
4734 subBorder.width = subWidth;
4735 subBorder.color = 0; // we are not interested in subBorder color
4736 bool firstWins;
4738 tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline,
4739 &firstWins);
4741 subElem = tempBorder.owner;
4742 subStyle = tempBorder.style;
4743 subWidth = tempBorder.width;
4744 if (!firstWins) {
4745 subSide = aSide;
4748 } else { // input args are dominant
4749 ownerSide = aSide;
4750 if (::Perpendicular(oldSide, LogicalSide(ownerSide))) {
4751 subElem = oldBorder.owner;
4752 subStyle = oldBorder.style;
4753 subWidth = oldBorder.width;
4754 subSide = oldSide;
4757 if (aBorder.width > 0) {
4758 numSegs++;
4759 if (!hasDashDot && ((StyleBorderStyle::Dashed == aBorder.style) ||
4760 (StyleBorderStyle::Dotted == aBorder.style))) {
4761 hasDashDot = 1;
4765 // bevel the corner if only two perpendicular non dashed/dotted segments
4766 // enter the corner
4767 bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot);
4771 struct BCCorners {
4772 BCCorners(int32_t aNumCorners, int32_t aStartIndex);
4774 BCCornerInfo& operator[](int32_t i) const {
4775 NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
4776 return corners[clamped(i, startIndex, endIndex) - startIndex];
4779 int32_t startIndex;
4780 int32_t endIndex;
4781 UniquePtr<BCCornerInfo[]> corners;
4784 BCCorners::BCCorners(int32_t aNumCorners, int32_t aStartIndex) {
4785 NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error");
4786 startIndex = aStartIndex;
4787 endIndex = aStartIndex + aNumCorners - 1;
4788 corners = MakeUnique<BCCornerInfo[]>(aNumCorners);
4791 struct BCCellBorders {
4792 BCCellBorders(int32_t aNumBorders, int32_t aStartIndex);
4794 BCCellBorder& operator[](int32_t i) const {
4795 NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
4796 return borders[clamped(i, startIndex, endIndex) - startIndex];
4799 int32_t startIndex;
4800 int32_t endIndex;
4801 UniquePtr<BCCellBorder[]> borders;
4804 BCCellBorders::BCCellBorders(int32_t aNumBorders, int32_t aStartIndex) {
4805 NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error");
4806 startIndex = aStartIndex;
4807 endIndex = aStartIndex + aNumBorders - 1;
4808 borders = MakeUnique<BCCellBorder[]>(aNumBorders);
4811 // this function sets the new border properties and returns true if the border
4812 // segment will start a new segment and not be accumulated into the previous
4813 // segment.
4814 static bool SetBorder(const BCCellBorder& aNewBorder, BCCellBorder& aBorder) {
4815 bool changed = (aNewBorder.style != aBorder.style) ||
4816 (aNewBorder.width != aBorder.width) ||
4817 (aNewBorder.color != aBorder.color);
4818 aBorder.color = aNewBorder.color;
4819 aBorder.width = aNewBorder.width;
4820 aBorder.style = aNewBorder.style;
4821 aBorder.owner = aNewBorder.owner;
4823 return changed;
4826 // this function will set the inline-dir border. It will return true if the
4827 // existing segment will not be continued. Having a block-dir owner of a corner
4828 // should also start a new segment.
4829 static bool SetInlineDirBorder(const BCCellBorder& aNewBorder,
4830 const BCCornerInfo& aCorner,
4831 BCCellBorder& aBorder) {
4832 bool startSeg = ::SetBorder(aNewBorder, aBorder);
4833 if (!startSeg) {
4834 startSeg = !IsInline(LogicalSide(aCorner.ownerSide));
4836 return startSeg;
4839 // Make the damage area larger on the top and bottom by at least one row and on
4840 // the left and right at least one column. This is done so that adjacent
4841 // elements are part of the border calculations. The extra segments and borders
4842 // outside the actual damage area will not be updated in the cell map, because
4843 // they in turn would need info from adjacent segments outside the damage area
4844 // to be accurate.
4845 void nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const {
4846 int32_t numRows = GetRowCount();
4847 int32_t numCols = GetColCount();
4849 int32_t dStartX = aArea.StartCol();
4850 int32_t dEndX = aArea.EndCol() - 1;
4851 int32_t dStartY = aArea.StartRow();
4852 int32_t dEndY = aArea.EndRow() - 1;
4854 // expand the damage area in each direction
4855 if (dStartX > 0) {
4856 dStartX--;
4858 if (dEndX < (numCols - 1)) {
4859 dEndX++;
4861 if (dStartY > 0) {
4862 dStartY--;
4864 if (dEndY < (numRows - 1)) {
4865 dEndY++;
4867 // Check the damage area so that there are no cells spanning in or out. If
4868 // there are any then make the damage area as big as the table, similarly to
4869 // the way the cell map decides whether to rebuild versus expand. This could
4870 // be optimized to expand to the smallest area that contains no spanners, but
4871 // it may not be worth the effort in general, and it would need to be done in
4872 // the cell map as well.
4873 bool haveSpanner = false;
4874 if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) ||
4875 (dEndY < (numRows - 1))) {
4876 nsTableCellMap* tableCellMap = GetCellMap();
4877 if (!tableCellMap) ABORT0();
4878 // Get the ordered row groups
4879 RowGroupArray rowGroups;
4880 OrderRowGroups(rowGroups);
4882 // Scope outside loop to be used as hint.
4883 nsCellMap* cellMap = nullptr;
4884 for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
4885 nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
4886 int32_t rgStartY = rgFrame->GetStartRowIndex();
4887 int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1;
4888 if (dEndY < rgStartY) break;
4889 cellMap = tableCellMap->GetMapFor(rgFrame, cellMap);
4890 if (!cellMap) ABORT0();
4891 // check for spanners from above and below
4892 if ((dStartY > 0) && (dStartY >= rgStartY) && (dStartY <= rgEndY)) {
4893 if (uint32_t(dStartY - rgStartY) >= cellMap->mRows.Length()) ABORT0();
4894 const nsCellMap::CellDataArray& row =
4895 cellMap->mRows[dStartY - rgStartY];
4896 for (int32_t x = dStartX; x <= dEndX; x++) {
4897 CellData* cellData = row.SafeElementAt(x);
4898 if (cellData && (cellData->IsRowSpan())) {
4899 haveSpanner = true;
4900 break;
4903 if (dEndY < rgEndY) {
4904 if (uint32_t(dEndY + 1 - rgStartY) >= cellMap->mRows.Length())
4905 ABORT0();
4906 const nsCellMap::CellDataArray& row2 =
4907 cellMap->mRows[dEndY + 1 - rgStartY];
4908 for (int32_t x = dStartX; x <= dEndX; x++) {
4909 CellData* cellData = row2.SafeElementAt(x);
4910 if (cellData && (cellData->IsRowSpan())) {
4911 haveSpanner = true;
4912 break;
4917 // check for spanners on the left and right
4918 int32_t iterStartY;
4919 int32_t iterEndY;
4920 if ((dStartY >= rgStartY) && (dStartY <= rgEndY)) {
4921 // the damage area starts in the row group
4922 iterStartY = dStartY;
4923 iterEndY = std::min(dEndY, rgEndY);
4924 } else if ((dEndY >= rgStartY) && (dEndY <= rgEndY)) {
4925 // the damage area ends in the row group
4926 iterStartY = rgStartY;
4927 iterEndY = dEndY;
4928 } else if ((rgStartY >= dStartY) && (rgEndY <= dEndY)) {
4929 // the damage area contains the row group
4930 iterStartY = rgStartY;
4931 iterEndY = rgEndY;
4932 } else {
4933 // the damage area does not overlap the row group
4934 continue;
4936 NS_ASSERTION(iterStartY >= 0 && iterEndY >= 0,
4937 "table index values are expected to be nonnegative");
4938 for (int32_t y = iterStartY; y <= iterEndY; y++) {
4939 if (uint32_t(y - rgStartY) >= cellMap->mRows.Length()) ABORT0();
4940 const nsCellMap::CellDataArray& row = cellMap->mRows[y - rgStartY];
4941 CellData* cellData = row.SafeElementAt(dStartX);
4942 if (cellData && (cellData->IsColSpan())) {
4943 haveSpanner = true;
4944 break;
4946 if (dEndX < (numCols - 1)) {
4947 cellData = row.SafeElementAt(dEndX + 1);
4948 if (cellData && (cellData->IsColSpan())) {
4949 haveSpanner = true;
4950 break;
4956 if (haveSpanner) {
4957 // make the damage area the whole table
4958 aArea.StartCol() = 0;
4959 aArea.StartRow() = 0;
4960 aArea.ColCount() = numCols;
4961 aArea.RowCount() = numRows;
4962 } else {
4963 aArea.StartCol() = dStartX;
4964 aArea.StartRow() = dStartY;
4965 aArea.ColCount() = 1 + dEndX - dStartX;
4966 aArea.RowCount() = 1 + dEndY - dStartY;
4970 #define ADJACENT true
4971 #define INLINE_DIR true
4973 void BCMapCellInfo::SetTableBStartIStartContBCBorder() {
4974 BCCellBorder currentBorder;
4975 // calculate continuous top first row & rowgroup border: special case
4976 // because it must include the table in the collapse
4977 if (mStartRow) {
4978 currentBorder =
4979 CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, mStartRow,
4980 nullptr, mTableWM, eLogicalSideBStart, !ADJACENT);
4981 mStartRow->SetContinuousBCBorderWidth(eLogicalSideBStart,
4982 currentBorder.width);
4984 if (mCgAtEnd && mColGroup) {
4985 // calculate continuous top colgroup border once per colgroup
4986 currentBorder =
4987 CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, mStartRow,
4988 nullptr, mTableWM, eLogicalSideBStart, !ADJACENT);
4989 mColGroup->SetContinuousBCBorderWidth(eLogicalSideBStart,
4990 currentBorder.width);
4992 if (0 == mColIndex) {
4993 currentBorder =
4994 CompareBorders(mTableFrame, mColGroup, mStartCol, nullptr, nullptr,
4995 nullptr, mTableWM, eLogicalSideIStart, !ADJACENT);
4996 mTableFrame->SetContinuousIStartBCBorderWidth(currentBorder.width);
5000 void BCMapCellInfo::SetRowGroupIStartContBCBorder() {
5001 BCCellBorder currentBorder;
5002 // get row group continuous borders
5003 if (mRgAtEnd && mRowGroup) { // once per row group, so check for bottom
5004 currentBorder =
5005 CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup, nullptr,
5006 nullptr, mTableWM, eLogicalSideIStart, !ADJACENT);
5007 mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIStart,
5008 currentBorder.width);
5012 void BCMapCellInfo::SetRowGroupIEndContBCBorder() {
5013 BCCellBorder currentBorder;
5014 // get row group continuous borders
5015 if (mRgAtEnd && mRowGroup) { // once per mRowGroup, so check for bottom
5016 currentBorder =
5017 CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup, nullptr,
5018 nullptr, mTableWM, eLogicalSideIEnd, ADJACENT);
5019 mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIEnd,
5020 currentBorder.width);
5024 void BCMapCellInfo::SetColumnBStartIEndContBCBorder() {
5025 BCCellBorder currentBorder;
5026 // calculate column continuous borders
5027 // we only need to do this once, so we'll do it only on the first row
5028 currentBorder = CompareBorders(
5029 mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, mRowGroup,
5030 mStartRow, nullptr, mTableWM, eLogicalSideBStart, !ADJACENT);
5031 mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBStart,
5032 currentBorder.width);
5033 if (mNumTableCols == GetCellEndColIndex() + 1) {
5034 currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
5035 mCurrentColFrame, nullptr, nullptr, nullptr,
5036 mTableWM, eLogicalSideIEnd, !ADJACENT);
5037 } else {
5038 currentBorder = CompareBorders(nullptr, mCurrentColGroupFrame,
5039 mCurrentColFrame, nullptr, nullptr, nullptr,
5040 mTableWM, eLogicalSideIEnd, !ADJACENT);
5042 mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd,
5043 currentBorder.width);
5046 void BCMapCellInfo::SetColumnBEndContBCBorder() {
5047 BCCellBorder currentBorder;
5048 // get col continuous border
5049 currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
5050 mCurrentColFrame, mRowGroup, mEndRow, nullptr,
5051 mTableWM, eLogicalSideBEnd, ADJACENT);
5052 mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBEnd,
5053 currentBorder.width);
5056 void BCMapCellInfo::SetColGroupBEndContBCBorder() {
5057 BCCellBorder currentBorder;
5058 if (mColGroup) {
5059 currentBorder =
5060 CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, mEndRow,
5061 nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
5062 mColGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd,
5063 currentBorder.width);
5067 void BCMapCellInfo::SetRowGroupBEndContBCBorder() {
5068 BCCellBorder currentBorder;
5069 if (mRowGroup) {
5070 currentBorder =
5071 CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, mEndRow,
5072 nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
5073 mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd,
5074 currentBorder.width);
5078 void BCMapCellInfo::SetInnerRowGroupBEndContBCBorder(
5079 const nsIFrame* aNextRowGroup, nsTableRowFrame* aNextRow) {
5080 BCCellBorder currentBorder, adjacentBorder;
5082 const nsIFrame* rowgroup = mRgAtEnd ? mRowGroup : nullptr;
5083 currentBorder = CompareBorders(nullptr, nullptr, nullptr, rowgroup, mEndRow,
5084 nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
5086 adjacentBorder =
5087 CompareBorders(nullptr, nullptr, nullptr, aNextRowGroup, aNextRow,
5088 nullptr, mTableWM, eLogicalSideBStart, !ADJACENT);
5089 currentBorder =
5090 CompareBorders(false, currentBorder, adjacentBorder, INLINE_DIR);
5091 if (aNextRow) {
5092 aNextRow->SetContinuousBCBorderWidth(eLogicalSideBStart,
5093 currentBorder.width);
5095 if (mRgAtEnd && mRowGroup) {
5096 mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd,
5097 currentBorder.width);
5101 void BCMapCellInfo::SetRowIStartContBCBorder() {
5102 // get row continuous borders
5103 if (mCurrentRowFrame) {
5104 BCCellBorder currentBorder;
5105 currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup,
5106 mCurrentRowFrame, nullptr, mTableWM,
5107 eLogicalSideIStart, !ADJACENT);
5108 mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIStart,
5109 currentBorder.width);
5113 void BCMapCellInfo::SetRowIEndContBCBorder() {
5114 if (mCurrentRowFrame) {
5115 BCCellBorder currentBorder;
5116 currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
5117 mCurrentRowFrame, nullptr, mTableWM,
5118 eLogicalSideIEnd, ADJACENT);
5119 mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd,
5120 currentBorder.width);
5123 void BCMapCellInfo::SetTableBStartBorderWidth(BCPixelSize aWidth) {
5124 mTableBCData->mBStartBorderWidth =
5125 std::max(mTableBCData->mBStartBorderWidth, aWidth);
5128 void BCMapCellInfo::SetTableIStartBorderWidth(int32_t aRowB,
5129 BCPixelSize aWidth) {
5130 // update the iStart first cell border
5131 if (aRowB == 0) {
5132 mTableBCData->mIStartCellBorderWidth = aWidth;
5134 mTableBCData->mIStartBorderWidth =
5135 std::max(mTableBCData->mIStartBorderWidth, aWidth);
5138 void BCMapCellInfo::SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth) {
5139 // update the iEnd first cell border
5140 if (aRowB == 0) {
5141 mTableBCData->mIEndCellBorderWidth = aWidth;
5143 mTableBCData->mIEndBorderWidth =
5144 std::max(mTableBCData->mIEndBorderWidth, aWidth);
5147 void BCMapCellInfo::SetIEndBorderWidths(BCPixelSize aWidth) {
5148 // update the borders of the cells and cols affected
5149 if (mCell) {
5150 mCell->SetBorderWidth(
5151 eLogicalSideIEnd,
5152 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideIEnd)));
5154 if (mEndCol) {
5155 BCPixelSize half = BC_BORDER_START_HALF(aWidth);
5156 mEndCol->SetIEndBorderWidth(std::max(half, mEndCol->GetIEndBorderWidth()));
5160 void BCMapCellInfo::SetBEndBorderWidths(BCPixelSize aWidth) {
5161 // update the borders of the affected cells and rows
5162 if (mCell) {
5163 mCell->SetBorderWidth(
5164 eLogicalSideBEnd,
5165 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideBEnd)));
5167 if (mEndRow) {
5168 BCPixelSize half = BC_BORDER_START_HALF(aWidth);
5169 mEndRow->SetBEndBCBorderWidth(
5170 std::max(half, mEndRow->GetBEndBCBorderWidth()));
5174 void BCMapCellInfo::SetBStartBorderWidths(BCPixelSize aWidth) {
5175 if (mCell) {
5176 mCell->SetBorderWidth(
5177 eLogicalSideBStart,
5178 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideBStart)));
5180 if (mStartRow) {
5181 BCPixelSize half = BC_BORDER_END_HALF(aWidth);
5182 mStartRow->SetBStartBCBorderWidth(
5183 std::max(half, mStartRow->GetBStartBCBorderWidth()));
5187 void BCMapCellInfo::SetIStartBorderWidths(BCPixelSize aWidth) {
5188 if (mCell) {
5189 mCell->SetBorderWidth(
5190 eLogicalSideIStart,
5191 std::max(aWidth, mCell->GetBorderWidth(eLogicalSideIStart)));
5193 if (mStartCol) {
5194 BCPixelSize half = BC_BORDER_END_HALF(aWidth);
5195 mStartCol->SetIStartBorderWidth(
5196 std::max(half, mStartCol->GetIStartBorderWidth()));
5200 void BCMapCellInfo::SetTableBEndBorderWidth(BCPixelSize aWidth) {
5201 mTableBCData->mBEndBorderWidth =
5202 std::max(mTableBCData->mBEndBorderWidth, aWidth);
5205 void BCMapCellInfo::SetColumn(int32_t aColX) {
5206 mCurrentColFrame = mTableFirstInFlow->GetColFrame(aColX);
5207 mCurrentColGroupFrame =
5208 static_cast<nsTableColGroupFrame*>(mCurrentColFrame->GetParent());
5209 if (!mCurrentColGroupFrame) {
5210 NS_ERROR("null mCurrentColGroupFrame");
5214 void BCMapCellInfo::IncrementRow(bool aResetToBStartRowOfCell) {
5215 mCurrentRowFrame =
5216 aResetToBStartRowOfCell ? mStartRow : mCurrentRowFrame->GetNextRow();
5219 BCCellBorder BCMapCellInfo::GetBStartEdgeBorder() {
5220 return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame,
5221 mRowGroup, mStartRow, mCell, mTableWM,
5222 eLogicalSideBStart, !ADJACENT);
5225 BCCellBorder BCMapCellInfo::GetBEndEdgeBorder() {
5226 return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame,
5227 mRowGroup, mEndRow, mCell, mTableWM, eLogicalSideBEnd,
5228 ADJACENT);
5230 BCCellBorder BCMapCellInfo::GetIStartEdgeBorder() {
5231 return CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup,
5232 mCurrentRowFrame, mCell, mTableWM, eLogicalSideIStart,
5233 !ADJACENT);
5235 BCCellBorder BCMapCellInfo::GetIEndEdgeBorder() {
5236 return CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
5237 mCurrentRowFrame, mCell, mTableWM, eLogicalSideIEnd,
5238 ADJACENT);
5240 BCCellBorder BCMapCellInfo::GetIEndInternalBorder() {
5241 const nsIFrame* cg = mCgAtEnd ? mColGroup : nullptr;
5242 return CompareBorders(nullptr, cg, mEndCol, nullptr, nullptr, mCell, mTableWM,
5243 eLogicalSideIEnd, ADJACENT);
5246 BCCellBorder BCMapCellInfo::GetIStartInternalBorder() {
5247 const nsIFrame* cg = mCgAtStart ? mColGroup : nullptr;
5248 return CompareBorders(nullptr, cg, mStartCol, nullptr, nullptr, mCell,
5249 mTableWM, eLogicalSideIStart, !ADJACENT);
5252 BCCellBorder BCMapCellInfo::GetBEndInternalBorder() {
5253 const nsIFrame* rg = mRgAtEnd ? mRowGroup : nullptr;
5254 return CompareBorders(nullptr, nullptr, nullptr, rg, mEndRow, mCell, mTableWM,
5255 eLogicalSideBEnd, ADJACENT);
5258 BCCellBorder BCMapCellInfo::GetBStartInternalBorder() {
5259 const nsIFrame* rg = mRgAtStart ? mRowGroup : nullptr;
5260 return CompareBorders(nullptr, nullptr, nullptr, rg, mStartRow, mCell,
5261 mTableWM, eLogicalSideBStart, !ADJACENT);
5264 /* XXX This comment is still written in physical (horizontal-tb) terms.
5266 Here is the order for storing border edges in the cell map as a cell is
5267 processed. There are n=colspan top and bottom border edges per cell and
5268 n=rowspan left and right border edges per cell.
5270 1) On the top edge of the table, store the top edge. Never store the top edge
5271 otherwise, since a bottom edge from a cell above will take care of it.
5273 2) On the left edge of the table, store the left edge. Never store the left
5274 edge othewise, since a right edge from a cell to the left will take care
5275 of it.
5277 3) Store the right edge (or edges if a row span)
5279 4) Store the bottom edge (or edges if a col span)
5281 Since corners are computed with only an array of BCCornerInfo indexed by the
5282 number-of-cols, corner calculations are somewhat complicated. Using an array
5283 with number-of-rows * number-of-col entries would simplify this, but at an
5284 extra in memory cost of nearly 12 bytes per cell map entry. Collapsing
5285 borders already have about an extra 8 byte per cell map entry overhead (this
5286 could be reduced to 4 bytes if we are willing to not store border widths in
5287 nsTableCellFrame), Here are the rules in priority order for storing cornes in
5288 the cell map as a cell is processed. top-left means the left endpoint of the
5289 border edge on the top of the cell. There are n=colspan top and bottom border
5290 edges per cell and n=rowspan left and right border edges per cell.
5292 1) On the top edge of the table, store the top-left corner, unless on the
5293 left edge of the table. Never store the top-right corner, since it will
5294 get stored as a right-top corner.
5296 2) On the left edge of the table, store the left-top corner. Never store the
5297 left-bottom corner, since it will get stored as a bottom-left corner.
5299 3) Store the right-top corner if (a) it is the top right corner of the table
5300 or (b) it is not on the top edge of the table. Never store the
5301 right-bottom corner since it will get stored as a bottom-right corner.
5303 4) Store the bottom-right corner, if it is the bottom right corner of the
5304 table. Never store it otherwise, since it will get stored as either a
5305 right-top corner by a cell below or a bottom-left corner from a cell to
5306 the right.
5308 5) Store the bottom-left corner, if (a) on the bottom edge of the table or
5309 (b) if the left edge hits the top side of a colspan in its interior.
5310 Never store the corner otherwise, since it will get stored as a right-top
5311 corner by a cell from below.
5313 XXX the BC-RTL hack - The correct fix would be a rewrite as described in bug
5314 203686. In order to draw borders in rtl conditions somehow correct, the
5315 existing structure which relies heavily on the assumption that the next cell
5316 sibling will be on the right side, has been modified. We flip the border
5317 during painting and during style lookup. Look for tableIsLTR for places where
5318 the flipping is done.
5321 // Calc the dominant border at every cell edge and corner within the current
5322 // damage area
5323 void nsTableFrame::CalcBCBorders() {
5324 NS_ASSERTION(IsBorderCollapse(),
5325 "calling CalcBCBorders on separated-border table");
5326 nsTableCellMap* tableCellMap = GetCellMap();
5327 if (!tableCellMap) ABORT0();
5328 int32_t numRows = GetRowCount();
5329 int32_t numCols = GetColCount();
5330 if (!numRows || !numCols) return; // nothing to do
5332 // Get the property holding the table damage area and border widths
5333 BCPropertyData* propData = GetBCProperty();
5334 if (!propData) ABORT0();
5336 // calculate an expanded damage area
5337 TableArea damageArea(propData->mDamageArea);
5338 ExpandBCDamageArea(damageArea);
5340 // segments that are on the table border edges need
5341 // to be initialized only once
5342 bool tableBorderReset[4];
5343 for (uint32_t sideX = 0; sideX < ArrayLength(tableBorderReset); sideX++) {
5344 tableBorderReset[sideX] = false;
5347 // block-dir borders indexed in inline-direction (cols)
5348 BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1,
5349 damageArea.StartCol());
5350 if (!lastBlockDirBorders.borders) ABORT0();
5351 BCCellBorder lastBStartBorder, lastBEndBorder;
5352 // inline-dir borders indexed in inline-direction (cols)
5353 BCCellBorders lastBEndBorders(damageArea.ColCount() + 1,
5354 damageArea.StartCol());
5355 if (!lastBEndBorders.borders) ABORT0();
5356 bool startSeg;
5357 bool gotRowBorder = false;
5359 BCMapCellInfo info(this), ajaInfo(this);
5361 BCCellBorder currentBorder, adjacentBorder;
5362 BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol());
5363 if (!bStartCorners.corners) ABORT0();
5364 BCCorners bEndCorners(damageArea.ColCount() + 1, damageArea.StartCol());
5365 if (!bEndCorners.corners) ABORT0();
5367 BCMapCellIterator iter(this, damageArea);
5368 for (iter.First(info); !iter.mAtEnd; iter.Next(info)) {
5369 // see if lastBStartBorder, lastBEndBorder need to be reset
5370 if (iter.IsNewRow()) {
5371 gotRowBorder = false;
5372 lastBStartBorder.Reset(info.mRowIndex, info.mRowSpan);
5373 lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
5374 } else if (info.mColIndex > damageArea.StartCol()) {
5375 lastBEndBorder = lastBEndBorders[info.mColIndex - 1];
5376 if (info.mRowIndex > (lastBEndBorder.rowIndex - lastBEndBorder.rowSpan)) {
5377 // the bStart border's iStart edge butts against the middle of a rowspan
5378 lastBStartBorder.Reset(info.mRowIndex, info.mRowSpan);
5380 if (lastBEndBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) {
5381 // the bEnd border's iStart edge butts against the middle of a rowspan
5382 lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
5386 // find the dominant border considering the cell's bStart border and the
5387 // table, row group, row if the border is at the bStart of the table,
5388 // otherwise it was processed in a previous row
5389 if (0 == info.mRowIndex) {
5390 if (!tableBorderReset[eLogicalSideBStart]) {
5391 propData->mBStartBorderWidth = 0;
5392 tableBorderReset[eLogicalSideBStart] = true;
5394 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
5395 colIdx++) {
5396 info.SetColumn(colIdx);
5397 currentBorder = info.GetBStartEdgeBorder();
5398 // update/store the bStart-iStart & bStart-iEnd corners of the seg
5399 BCCornerInfo& tlCorner = bStartCorners[colIdx]; // bStart-iStart
5400 if (0 == colIdx) {
5401 // we are on the iEnd side of the corner
5402 tlCorner.Set(eLogicalSideIEnd, currentBorder);
5403 } else {
5404 tlCorner.Update(eLogicalSideIEnd, currentBorder);
5405 tableCellMap->SetBCBorderCorner(eLogicalCornerBStartIStart,
5406 *iter.mCellMap, 0, 0, colIdx,
5407 LogicalSide(tlCorner.ownerSide),
5408 tlCorner.subWidth, tlCorner.bevel);
5410 bStartCorners[colIdx + 1].Set(eLogicalSideIStart,
5411 currentBorder); // bStart-iEnd
5412 // update lastBStartBorder and see if a new segment starts
5413 startSeg =
5414 SetInlineDirBorder(currentBorder, tlCorner, lastBStartBorder);
5415 // store the border segment in the cell map
5416 tableCellMap->SetBCBorderEdge(eLogicalSideBStart, *iter.mCellMap, 0, 0,
5417 colIdx, 1, currentBorder.owner,
5418 currentBorder.width, startSeg);
5420 info.SetTableBStartBorderWidth(currentBorder.width);
5421 info.SetBStartBorderWidths(currentBorder.width);
5422 info.SetColumnBStartIEndContBCBorder();
5424 info.SetTableBStartIStartContBCBorder();
5425 } else {
5426 // see if the bStart border needs to be the start of a segment due to a
5427 // block-dir border owning the corner
5428 if (info.mColIndex > 0) {
5429 BCData& data = info.mCellData->mData;
5430 if (!data.IsBStartStart()) {
5431 LogicalSide cornerSide;
5432 bool bevel;
5433 data.GetCorner(cornerSide, bevel);
5434 if (IsBlock(cornerSide)) {
5435 data.SetBStartStart(true);
5441 // find the dominant border considering the cell's iStart border and the
5442 // table, col group, col if the border is at the iStart of the table,
5443 // otherwise it was processed in a previous col
5444 if (0 == info.mColIndex) {
5445 if (!tableBorderReset[eLogicalSideIStart]) {
5446 propData->mIStartBorderWidth = 0;
5447 tableBorderReset[eLogicalSideIStart] = true;
5449 info.mCurrentRowFrame = nullptr;
5450 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
5451 rowB++) {
5452 info.IncrementRow(rowB == info.mRowIndex);
5453 currentBorder = info.GetIStartEdgeBorder();
5454 BCCornerInfo& tlCorner =
5455 (0 == rowB) ? bStartCorners[0] : bEndCorners[0];
5456 tlCorner.Update(eLogicalSideBEnd, currentBorder);
5457 tableCellMap->SetBCBorderCorner(
5458 eLogicalCornerBStartIStart, *iter.mCellMap, iter.mRowGroupStart,
5459 rowB, 0, LogicalSide(tlCorner.ownerSide), tlCorner.subWidth,
5460 tlCorner.bevel);
5461 bEndCorners[0].Set(eLogicalSideBStart, currentBorder); // bEnd-iStart
5463 // update lastBlockDirBorders and see if a new segment starts
5464 startSeg = SetBorder(currentBorder, lastBlockDirBorders[0]);
5465 // store the border segment in the cell map
5466 tableCellMap->SetBCBorderEdge(eLogicalSideIStart, *iter.mCellMap,
5467 iter.mRowGroupStart, rowB, info.mColIndex,
5468 1, currentBorder.owner,
5469 currentBorder.width, startSeg);
5470 info.SetTableIStartBorderWidth(rowB, currentBorder.width);
5471 info.SetIStartBorderWidths(currentBorder.width);
5472 info.SetRowIStartContBCBorder();
5474 info.SetRowGroupIStartContBCBorder();
5477 // find the dominant border considering the cell's iEnd border, adjacent
5478 // cells and the table, row group, row
5479 if (info.mNumTableCols == info.GetCellEndColIndex() + 1) {
5480 // touches iEnd edge of table
5481 if (!tableBorderReset[eLogicalSideIEnd]) {
5482 propData->mIEndBorderWidth = 0;
5483 tableBorderReset[eLogicalSideIEnd] = true;
5485 info.mCurrentRowFrame = nullptr;
5486 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
5487 rowB++) {
5488 info.IncrementRow(rowB == info.mRowIndex);
5489 currentBorder = info.GetIEndEdgeBorder();
5490 // update/store the bStart-iEnd & bEnd-iEnd corners
5491 BCCornerInfo& trCorner =
5492 (0 == rowB) ? bStartCorners[info.GetCellEndColIndex() + 1]
5493 : bEndCorners[info.GetCellEndColIndex() + 1];
5494 trCorner.Update(eLogicalSideBEnd, currentBorder); // bStart-iEnd
5495 tableCellMap->SetBCBorderCorner(
5496 eLogicalCornerBStartIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5497 info.GetCellEndColIndex(), LogicalSide(trCorner.ownerSide),
5498 trCorner.subWidth, trCorner.bevel);
5499 BCCornerInfo& brCorner = bEndCorners[info.GetCellEndColIndex() + 1];
5500 brCorner.Set(eLogicalSideBStart, currentBorder); // bEnd-iEnd
5501 tableCellMap->SetBCBorderCorner(
5502 eLogicalCornerBEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5503 info.GetCellEndColIndex(), LogicalSide(brCorner.ownerSide),
5504 brCorner.subWidth, brCorner.bevel);
5505 // update lastBlockDirBorders and see if a new segment starts
5506 startSeg = SetBorder(
5507 currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
5508 // store the border segment in the cell map and update cellBorders
5509 tableCellMap->SetBCBorderEdge(
5510 eLogicalSideIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5511 info.GetCellEndColIndex(), 1, currentBorder.owner,
5512 currentBorder.width, startSeg);
5513 info.SetTableIEndBorderWidth(rowB, currentBorder.width);
5514 info.SetIEndBorderWidths(currentBorder.width);
5515 info.SetRowIEndContBCBorder();
5517 info.SetRowGroupIEndContBCBorder();
5518 } else {
5519 int32_t segLength = 0;
5520 BCMapCellInfo priorAjaInfo(this);
5521 for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
5522 rowB += segLength) {
5523 iter.PeekIEnd(info, rowB, ajaInfo);
5524 currentBorder = info.GetIEndInternalBorder();
5525 adjacentBorder = ajaInfo.GetIStartInternalBorder();
5526 currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
5527 adjacentBorder, !INLINE_DIR);
5529 segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowB);
5530 segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowB);
5532 // update lastBlockDirBorders and see if a new segment starts
5533 startSeg = SetBorder(
5534 currentBorder, lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
5535 // store the border segment in the cell map and update cellBorders
5536 if (info.GetCellEndColIndex() < damageArea.EndCol() &&
5537 rowB >= damageArea.StartRow() && rowB < damageArea.EndRow()) {
5538 tableCellMap->SetBCBorderEdge(
5539 eLogicalSideIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB,
5540 info.GetCellEndColIndex(), segLength, currentBorder.owner,
5541 currentBorder.width, startSeg);
5542 info.SetIEndBorderWidths(currentBorder.width);
5543 ajaInfo.SetIStartBorderWidths(currentBorder.width);
5545 // update the bStart-iEnd corner
5546 bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) &&
5547 (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
5548 BCCornerInfo* trCorner =
5549 ((0 == rowB) || hitsSpanOnIEnd)
5550 ? &bStartCorners[info.GetCellEndColIndex() + 1]
5551 : &bEndCorners[info.GetCellEndColIndex() + 1];
5552 trCorner->Update(eLogicalSideBEnd, currentBorder);
5553 // if this is not the first time through,
5554 // consider the segment to the iEnd side
5555 if (rowB != info.mRowIndex) {
5556 currentBorder = priorAjaInfo.GetBEndInternalBorder();
5557 adjacentBorder = ajaInfo.GetBStartInternalBorder();
5558 currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
5559 adjacentBorder, INLINE_DIR);
5560 trCorner->Update(eLogicalSideIEnd, currentBorder);
5562 // store the bStart-iEnd corner in the cell map
5563 if (info.GetCellEndColIndex() < damageArea.EndCol() &&
5564 rowB >= damageArea.StartRow()) {
5565 if (0 != rowB) {
5566 tableCellMap->SetBCBorderCorner(
5567 eLogicalCornerBStartIEnd, *iter.mCellMap, iter.mRowGroupStart,
5568 rowB, info.GetCellEndColIndex(),
5569 LogicalSide(trCorner->ownerSide), trCorner->subWidth,
5570 trCorner->bevel);
5572 // store any corners this cell spans together with the aja cell
5573 for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) {
5574 tableCellMap->SetBCBorderCorner(
5575 eLogicalCornerBEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rX,
5576 info.GetCellEndColIndex(), LogicalSide(trCorner->ownerSide),
5577 trCorner->subWidth, false);
5580 // update bEnd-iEnd corner, bStartCorners, bEndCorners
5581 hitsSpanOnIEnd =
5582 (rowB + segLength < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
5583 BCCornerInfo& brCorner =
5584 (hitsSpanOnIEnd) ? bStartCorners[info.GetCellEndColIndex() + 1]
5585 : bEndCorners[info.GetCellEndColIndex() + 1];
5586 brCorner.Set(eLogicalSideBStart, currentBorder);
5587 priorAjaInfo = ajaInfo;
5590 for (int32_t colIdx = info.mColIndex + 1;
5591 colIdx <= info.GetCellEndColIndex(); colIdx++) {
5592 lastBlockDirBorders[colIdx].Reset(0, 1);
5595 // find the dominant border considering the cell's bEnd border, adjacent
5596 // cells and the table, row group, row
5597 if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) {
5598 // touches bEnd edge of table
5599 if (!tableBorderReset[eLogicalSideBEnd]) {
5600 propData->mBEndBorderWidth = 0;
5601 tableBorderReset[eLogicalSideBEnd] = true;
5603 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
5604 colIdx++) {
5605 info.SetColumn(colIdx);
5606 currentBorder = info.GetBEndEdgeBorder();
5607 // update/store the bEnd-iStart & bEnd-IEnd corners
5608 BCCornerInfo& blCorner = bEndCorners[colIdx]; // bEnd-iStart
5609 blCorner.Update(eLogicalSideIEnd, currentBorder);
5610 tableCellMap->SetBCBorderCorner(
5611 eLogicalCornerBEndIStart, *iter.mCellMap, iter.mRowGroupStart,
5612 info.GetCellEndRowIndex(), colIdx, LogicalSide(blCorner.ownerSide),
5613 blCorner.subWidth, blCorner.bevel);
5614 BCCornerInfo& brCorner = bEndCorners[colIdx + 1]; // bEnd-iEnd
5615 brCorner.Update(eLogicalSideIStart, currentBorder);
5616 if (info.mNumTableCols ==
5617 colIdx + 1) { // bEnd-IEnd corner of the table
5618 tableCellMap->SetBCBorderCorner(
5619 eLogicalCornerBEndIEnd, *iter.mCellMap, iter.mRowGroupStart,
5620 info.GetCellEndRowIndex(), colIdx,
5621 LogicalSide(brCorner.ownerSide), brCorner.subWidth,
5622 brCorner.bevel, true);
5624 // update lastBEndBorder and see if a new segment starts
5625 startSeg = SetInlineDirBorder(currentBorder, blCorner, lastBEndBorder);
5626 if (!startSeg) {
5627 // make sure that we did not compare apples to oranges i.e. the
5628 // current border should be a continuation of the lastBEndBorder,
5629 // as it is a bEnd border
5630 // add 1 to the info.GetCellEndRowIndex()
5631 startSeg =
5632 (lastBEndBorder.rowIndex != (info.GetCellEndRowIndex() + 1));
5634 // store the border segment in the cell map and update cellBorders
5635 tableCellMap->SetBCBorderEdge(
5636 eLogicalSideBEnd, *iter.mCellMap, iter.mRowGroupStart,
5637 info.GetCellEndRowIndex(), colIdx, 1, currentBorder.owner,
5638 currentBorder.width, startSeg);
5639 // update lastBEndBorders
5640 lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
5641 lastBEndBorder.rowSpan = info.mRowSpan;
5642 lastBEndBorders[colIdx] = lastBEndBorder;
5644 info.SetBEndBorderWidths(currentBorder.width);
5645 info.SetTableBEndBorderWidth(currentBorder.width);
5646 info.SetColumnBEndContBCBorder();
5648 info.SetRowGroupBEndContBCBorder();
5649 info.SetColGroupBEndContBCBorder();
5650 } else {
5651 int32_t segLength = 0;
5652 for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex();
5653 colIdx += segLength) {
5654 iter.PeekBEnd(info, colIdx, ajaInfo);
5655 currentBorder = info.GetBEndInternalBorder();
5656 adjacentBorder = ajaInfo.GetBStartInternalBorder();
5657 currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
5658 adjacentBorder, INLINE_DIR);
5659 segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colIdx);
5660 segLength =
5661 std::min(segLength, info.mColIndex + info.mColSpan - colIdx);
5663 // update, store the bEnd-iStart corner
5664 BCCornerInfo& blCorner = bEndCorners[colIdx]; // bEnd-iStart
5665 bool hitsSpanBelow = (colIdx > ajaInfo.mColIndex) &&
5666 (colIdx < ajaInfo.mColIndex + ajaInfo.mColSpan);
5667 bool update = true;
5668 if (colIdx == info.mColIndex && colIdx > damageArea.StartCol()) {
5669 int32_t prevRowIndex = lastBEndBorders[colIdx - 1].rowIndex;
5670 if (prevRowIndex > info.GetCellEndRowIndex() + 1) {
5671 // hits a rowspan on the iEnd side
5672 update = false;
5673 // the corner was taken care of during the cell on the iStart side
5674 } else if (prevRowIndex < info.GetCellEndRowIndex() + 1) {
5675 // spans below the cell to the iStart side
5676 bStartCorners[colIdx] = blCorner;
5677 blCorner.Set(eLogicalSideIEnd, currentBorder);
5678 update = false;
5681 if (update) {
5682 blCorner.Update(eLogicalSideIEnd, currentBorder);
5684 if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
5685 colIdx >= damageArea.StartCol()) {
5686 if (hitsSpanBelow) {
5687 tableCellMap->SetBCBorderCorner(eLogicalCornerBEndIStart,
5688 *iter.mCellMap, iter.mRowGroupStart,
5689 info.GetCellEndRowIndex(), colIdx,
5690 LogicalSide(blCorner.ownerSide),
5691 blCorner.subWidth, blCorner.bevel);
5693 // store any corners this cell spans together with the aja cell
5694 for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) {
5695 BCCornerInfo& corner = bEndCorners[c];
5696 corner.Set(eLogicalSideIEnd, currentBorder);
5697 tableCellMap->SetBCBorderCorner(
5698 eLogicalCornerBEndIStart, *iter.mCellMap, iter.mRowGroupStart,
5699 info.GetCellEndRowIndex(), c, LogicalSide(corner.ownerSide),
5700 corner.subWidth, false);
5703 // update lastBEndBorders and see if a new segment starts
5704 startSeg = SetInlineDirBorder(currentBorder, blCorner, lastBEndBorder);
5705 if (!startSeg) {
5706 // make sure that we did not compare apples to oranges i.e. the
5707 // current border should be a continuation of the lastBEndBorder,
5708 // as it is a bEnd border
5709 // add 1 to the info.GetCellEndRowIndex()
5710 startSeg = (lastBEndBorder.rowIndex != info.GetCellEndRowIndex() + 1);
5712 lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
5713 lastBEndBorder.rowSpan = info.mRowSpan;
5714 for (int32_t c = colIdx; c < colIdx + segLength; c++) {
5715 lastBEndBorders[c] = lastBEndBorder;
5718 // store the border segment the cell map and update cellBorders
5719 if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
5720 colIdx >= damageArea.StartCol() && colIdx < damageArea.EndCol()) {
5721 tableCellMap->SetBCBorderEdge(
5722 eLogicalSideBEnd, *iter.mCellMap, iter.mRowGroupStart,
5723 info.GetCellEndRowIndex(), colIdx, segLength, currentBorder.owner,
5724 currentBorder.width, startSeg);
5725 info.SetBEndBorderWidths(currentBorder.width);
5726 ajaInfo.SetBStartBorderWidths(currentBorder.width);
5728 // update bEnd-iEnd corner
5729 BCCornerInfo& brCorner = bEndCorners[colIdx + segLength];
5730 brCorner.Update(eLogicalSideIStart, currentBorder);
5732 if (!gotRowBorder && 1 == info.mRowSpan &&
5733 (ajaInfo.mStartRow || info.mRgAtEnd)) {
5734 // get continuous row/row group border
5735 // we need to check the row group's bEnd border if this is
5736 // the last row in the row group, but only a cell with rowspan=1
5737 // will know whether *this* row is at the bEnd
5738 const nsIFrame* nextRowGroup =
5739 ajaInfo.mRgAtStart ? ajaInfo.mRowGroup : nullptr;
5740 info.SetInnerRowGroupBEndContBCBorder(nextRowGroup, ajaInfo.mStartRow);
5741 gotRowBorder = true;
5744 // In the function, we try to join two cells' BEnd.
5745 // We normally do this work when processing the cell on the iEnd side,
5746 // but when the cell on the iEnd side has a rowspan, the cell on the
5747 // iStart side gets processed later (now), so we have to do this work now.
5748 const auto nextColIndex = info.GetCellEndColIndex() + 1;
5749 if ((info.mNumTableCols != nextColIndex) &&
5750 (lastBEndBorders[nextColIndex].rowSpan > 1) &&
5751 (lastBEndBorders[nextColIndex].rowIndex ==
5752 info.GetCellEndRowIndex() + 1)) {
5753 BCCornerInfo& corner = bEndCorners[nextColIndex];
5754 if (!IsBlock(LogicalSide(corner.ownerSide))) {
5755 // not a block-dir owner
5756 BCCellBorder& thisBorder = lastBEndBorder;
5757 BCCellBorder& nextBorder = lastBEndBorders[info.mColIndex + 1];
5758 if ((thisBorder.color == nextBorder.color) &&
5759 (thisBorder.width == nextBorder.width) &&
5760 (thisBorder.style == nextBorder.style)) {
5761 // set the flag on the next border indicating it is not the start of a
5762 // new segment
5763 if (iter.mCellMap) {
5764 tableCellMap->ResetBStartStart(
5765 eLogicalSideBEnd, *iter.mCellMap, iter.mRowGroupStart,
5766 info.GetCellEndRowIndex(), nextColIndex);
5771 } // for (iter.First(info); info.mCell; iter.Next(info)) {
5772 // reset the bc flag and damage area
5773 SetNeedToCalcBCBorders(false);
5774 propData->mDamageArea = TableArea(0, 0, 0, 0);
5775 #ifdef DEBUG_TABLE_CELLMAP
5776 mCellMap->Dump();
5777 #endif
5780 class BCPaintBorderIterator;
5782 struct BCBorderParameters {
5783 StyleBorderStyle mBorderStyle;
5784 nscolor mBorderColor;
5785 nsRect mBorderRect;
5786 int32_t mAppUnitsPerDevPixel;
5787 mozilla::Side mStartBevelSide;
5788 nscoord mStartBevelOffset;
5789 mozilla::Side mEndBevelSide;
5790 nscoord mEndBevelOffset;
5791 bool mBackfaceIsVisible;
5793 bool NeedToBevel() const {
5794 if (!mStartBevelOffset && !mEndBevelOffset) {
5795 return false;
5798 if (mBorderStyle == StyleBorderStyle::Dashed ||
5799 mBorderStyle == StyleBorderStyle::Dotted) {
5800 return false;
5803 return true;
5807 struct BCBlockDirSeg {
5808 BCBlockDirSeg();
5810 void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner,
5811 BCPixelSize aBlockSegISize, BCPixelSize aInlineSegBSize,
5812 Maybe<nscoord> aEmptyRowEndSize);
5814 void Initialize(BCPaintBorderIterator& aIter);
5815 void GetBEndCorner(BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize);
5817 Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter,
5818 BCPixelSize aInlineSegBSize);
5819 void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget,
5820 BCPixelSize aInlineSegBSize);
5821 void CreateWebRenderCommands(BCPaintBorderIterator& aIter,
5822 BCPixelSize aInlineSegBSize,
5823 wr::DisplayListBuilder& aBuilder,
5824 const layers::StackingContextHelper& aSc,
5825 const nsPoint& aPt);
5826 void AdvanceOffsetB();
5827 void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
5829 union {
5830 nsTableColFrame* mCol;
5831 int32_t mColWidth;
5833 nscoord mOffsetI; // i-offset with respect to the table edge
5834 nscoord mOffsetB; // b-offset with respect to the table edge
5835 nscoord mLength; // block-dir length including corners
5836 BCPixelSize mWidth; // thickness in pixels
5838 nsTableCellFrame* mAjaCell; // previous sibling to the first cell
5839 // where the segment starts, it can be
5840 // the owner of a segment
5841 nsTableCellFrame* mFirstCell; // cell at the start of the segment
5842 nsTableRowGroupFrame*
5843 mFirstRowGroup; // row group at the start of the segment
5844 nsTableRowFrame* mFirstRow; // row at the start of the segment
5845 nsTableCellFrame* mLastCell; // cell at the current end of the
5846 // segment
5848 uint8_t mOwner; // owner of the border, defines the
5849 // style
5850 LogicalSide mBStartBevelSide; // direction to bevel at the bStart
5851 nscoord mBStartBevelOffset; // how much to bevel at the bStart
5852 BCPixelSize mBEndInlineSegBSize; // bSize of the crossing
5853 // inline-dir border
5854 nscoord mBEndOffset; // how much longer is the segment due
5855 // to the inline-dir border, by this
5856 // amount the next segment needs to be
5857 // shifted.
5858 bool mIsBEndBevel; // should we bevel at the bEnd
5861 struct BCInlineDirSeg {
5862 BCInlineDirSeg();
5864 void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner,
5865 BCPixelSize aBEndBlockSegISize, BCPixelSize aInlineSegBSize);
5866 void GetIEndCorner(BCPaintBorderIterator& aIter, BCPixelSize aIStartSegISize);
5867 void AdvanceOffsetI();
5868 void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
5869 Maybe<BCBorderParameters> BuildBorderParameters(BCPaintBorderIterator& aIter);
5870 void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget);
5871 void CreateWebRenderCommands(BCPaintBorderIterator& aIter,
5872 wr::DisplayListBuilder& aBuilder,
5873 const layers::StackingContextHelper& aSc,
5874 const nsPoint& aPt);
5876 nscoord mOffsetI; // i-offset with respect to the table edge
5877 nscoord mOffsetB; // b-offset with respect to the table edge
5878 nscoord mLength; // inline-dir length including corners
5879 BCPixelSize mWidth; // border thickness in pixels
5880 nscoord mIStartBevelOffset; // how much to bevel at the iStart
5881 LogicalSide mIStartBevelSide; // direction to bevel at the iStart
5882 bool mIsIEndBevel; // should we bevel at the iEnd end
5883 nscoord mIEndBevelOffset; // how much to bevel at the iEnd
5884 LogicalSide mIEndBevelSide; // direction to bevel at the iEnd
5885 nscoord mEndOffset; // how much longer is the segment due
5886 // to the block-dir border, by this
5887 // amount the next segment needs to be
5888 // shifted.
5889 uint8_t mOwner; // owner of the border, defines the
5890 // style
5891 nsTableCellFrame* mFirstCell; // cell at the start of the segment
5892 nsTableCellFrame* mAjaCell; // neighboring cell to the first cell
5893 // where the segment starts, it can be
5894 // the owner of a segment
5897 struct BCPaintData {
5898 explicit BCPaintData(DrawTarget& aDrawTarget) : mDrawTarget(aDrawTarget) {}
5900 DrawTarget& mDrawTarget;
5903 struct BCCreateWebRenderCommandsData {
5904 BCCreateWebRenderCommandsData(wr::DisplayListBuilder& aBuilder,
5905 const layers::StackingContextHelper& aSc,
5906 const nsPoint& aOffsetToReferenceFrame)
5907 : mBuilder(aBuilder),
5908 mSc(aSc),
5909 mOffsetToReferenceFrame(aOffsetToReferenceFrame) {}
5911 wr::DisplayListBuilder& mBuilder;
5912 const layers::StackingContextHelper& mSc;
5913 const nsPoint& mOffsetToReferenceFrame;
5916 struct BCPaintBorderAction {
5917 explicit BCPaintBorderAction(DrawTarget& aDrawTarget)
5918 : mMode(Mode::Paint), mPaintData(aDrawTarget) {}
5920 BCPaintBorderAction(wr::DisplayListBuilder& aBuilder,
5921 const layers::StackingContextHelper& aSc,
5922 const nsPoint& aOffsetToReferenceFrame)
5923 : mMode(Mode::CreateWebRenderCommands),
5924 mCreateWebRenderCommandsData(aBuilder, aSc, aOffsetToReferenceFrame) {}
5926 ~BCPaintBorderAction() {
5927 // mCreateWebRenderCommandsData is in a union which means the destructor
5928 // wouldn't be called when BCPaintBorderAction get destroyed. So call the
5929 // destructor here explicitly.
5930 if (mMode == Mode::CreateWebRenderCommands) {
5931 mCreateWebRenderCommandsData.~BCCreateWebRenderCommandsData();
5935 enum class Mode {
5936 Paint,
5937 CreateWebRenderCommands,
5940 Mode mMode;
5942 union {
5943 BCPaintData mPaintData;
5944 BCCreateWebRenderCommandsData mCreateWebRenderCommandsData;
5948 // Iterates over borders (iStart border, corner, bStart border) in the cell map
5949 // within a damage area from iStart to iEnd, bStart to bEnd. All members are in
5950 // terms of the 1st in flow frames, except where suffixed by InFlow.
5951 class BCPaintBorderIterator {
5952 public:
5953 explicit BCPaintBorderIterator(nsTableFrame* aTable);
5954 void Reset();
5957 * Determine the damage area in terms of rows and columns and finalize
5958 * mInitialOffsetI and mInitialOffsetB.
5959 * @param aDirtyRect - dirty rect in table coordinates
5960 * @return - true if we need to paint something given dirty rect
5962 bool SetDamageArea(const nsRect& aDamageRect);
5963 void First();
5964 void Next();
5965 void AccumulateOrDoActionInlineDirSegment(BCPaintBorderAction& aAction);
5966 void AccumulateOrDoActionBlockDirSegment(BCPaintBorderAction& aAction);
5967 void ResetVerInfo();
5968 void StoreColumnWidth(int32_t aIndex);
5969 bool BlockDirSegmentOwnsCorner();
5971 nsTableFrame* mTable;
5972 nsTableFrame* mTableFirstInFlow;
5973 nsTableCellMap* mTableCellMap;
5974 nsCellMap* mCellMap;
5975 WritingMode mTableWM;
5976 nsTableFrame::RowGroupArray mRowGroups;
5978 nsTableRowGroupFrame* mPrevRg;
5979 nsTableRowGroupFrame* mRg;
5980 bool mIsRepeatedHeader;
5981 bool mIsRepeatedFooter;
5982 nsTableRowGroupFrame* mStartRg; // first row group in the damagearea
5983 int32_t mRgIndex; // current row group index in the
5984 // mRowgroups array
5985 int32_t mFifRgFirstRowIndex; // start row index of the first in
5986 // flow of the row group
5987 int32_t mRgFirstRowIndex; // row index of the first row in the
5988 // row group
5989 int32_t mRgLastRowIndex; // row index of the last row in the row
5990 // group
5991 int32_t mNumTableRows; // number of rows in the table and all
5992 // continuations
5993 int32_t mNumTableCols; // number of columns in the table
5994 int32_t mColIndex; // with respect to the table
5995 int32_t mRowIndex; // with respect to the table
5996 int32_t mRepeatedHeaderRowIndex; // row index in a repeated
5997 // header, it's equivalent to
5998 // mRowIndex when we're in a repeated
5999 // header, and set to the last row
6000 // index of a repeated header when
6001 // we're not
6002 bool mIsNewRow;
6003 bool mAtEnd; // the iterator cycled over all
6004 // borders
6005 nsTableRowFrame* mPrevRow;
6006 nsTableRowFrame* mRow;
6007 nsTableRowFrame* mStartRow; // first row in a inside the damagearea
6009 // cell properties
6010 nsTableCellFrame* mPrevCell;
6011 nsTableCellFrame* mCell;
6012 BCCellData* mPrevCellData;
6013 BCCellData* mCellData;
6014 BCData* mBCData;
6016 bool IsTableBStartMost() {
6017 return (mRowIndex == 0) && !mTable->GetPrevInFlow();
6019 bool IsTableIEndMost() { return (mColIndex >= mNumTableCols); }
6020 bool IsTableBEndMost() {
6021 return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();
6023 bool IsTableIStartMost() { return (mColIndex == 0); }
6024 bool IsDamageAreaBStartMost() const {
6025 return mRowIndex == mDamageArea.StartRow();
6027 bool IsDamageAreaIEndMost() const {
6028 return mColIndex >= mDamageArea.EndCol();
6030 bool IsDamageAreaBEndMost() const {
6031 return mRowIndex >= mDamageArea.EndRow();
6033 bool IsDamageAreaIStartMost() const {
6034 return mColIndex == mDamageArea.StartCol();
6036 int32_t GetRelativeColIndex() const {
6037 return mColIndex - mDamageArea.StartCol();
6040 TableArea mDamageArea; // damageArea in cellmap coordinates
6041 bool IsAfterRepeatedHeader() {
6042 return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1));
6044 bool StartRepeatedFooter() const {
6045 return mIsRepeatedFooter && mRowIndex == mRgFirstRowIndex &&
6046 mRowIndex != mDamageArea.StartRow();
6049 nscoord mInitialOffsetI; // offsetI of the first border with
6050 // respect to the table
6051 nscoord mInitialOffsetB; // offsetB of the first border with
6052 // respect to the table
6053 nscoord mNextOffsetB; // offsetB of the next segment
6054 // this array is used differently when
6055 // inline-dir and block-dir borders are drawn
6056 // When inline-dir border are drawn we cache
6057 // the column widths and the width of the
6058 // block-dir borders that arrive from bStart
6059 // When we draw block-dir borders we store
6060 // lengths and width for block-dir borders
6061 // before they are drawn while we move over
6062 // the columns in the damage area
6063 // It has one more elements than columns are
6064 // in the table.
6065 UniquePtr<BCBlockDirSeg[]> mBlockDirInfo;
6066 BCInlineDirSeg mInlineSeg; // the inline-dir segment while we
6067 // move over the colums
6068 BCPixelSize mPrevInlineSegBSize; // the bSize of the previous
6069 // inline-dir border
6071 private:
6072 bool SetNewRow(nsTableRowFrame* aRow = nullptr);
6073 bool SetNewRowGroup();
6074 void SetNewData(int32_t aRowIndex, int32_t aColIndex);
6077 BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable)
6078 : mTable(aTable),
6079 mTableFirstInFlow(static_cast<nsTableFrame*>(aTable->FirstInFlow())),
6080 mTableCellMap(aTable->GetCellMap()),
6081 mCellMap(nullptr),
6082 mTableWM(aTable->Style()),
6083 mPrevRg(nullptr),
6084 mRg(nullptr),
6085 mIsRepeatedHeader(false),
6086 mIsRepeatedFooter(false),
6087 mStartRg(nullptr),
6088 mRgIndex(0),
6089 mFifRgFirstRowIndex(0),
6090 mRgFirstRowIndex(0),
6091 mRgLastRowIndex(0),
6092 mColIndex(0),
6093 mRowIndex(0),
6094 mIsNewRow(false),
6095 mAtEnd(false),
6096 mPrevRow(nullptr),
6097 mRow(nullptr),
6098 mStartRow(nullptr),
6099 mPrevCell(nullptr),
6100 mCell(nullptr),
6101 mPrevCellData(nullptr),
6102 mCellData(nullptr),
6103 mBCData(nullptr),
6104 mInitialOffsetI(0),
6105 mNextOffsetB(0),
6106 mPrevInlineSegBSize(0) {
6107 LogicalMargin childAreaOffset = mTable->GetChildAreaOffset(mTableWM, nullptr);
6108 // y position of first row in damage area
6109 mInitialOffsetB =
6110 mTable->GetPrevInFlow() ? 0 : childAreaOffset.BStart(mTableWM);
6111 mNumTableRows = mTable->GetRowCount();
6112 mNumTableCols = mTable->GetColCount();
6114 // Get the ordered row groups
6115 mTable->OrderRowGroups(mRowGroups);
6116 // initialize to a non existing index
6117 mRepeatedHeaderRowIndex = -99;
6120 bool BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) {
6121 nsSize containerSize = mTable->GetSize();
6122 LogicalRect dirtyRect(mTableWM, aDirtyRect, containerSize);
6123 uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex;
6124 startRowIndex = endRowIndex = startColIndex = endColIndex = 0;
6125 bool done = false;
6126 bool haveIntersect = false;
6127 // find startRowIndex, endRowIndex
6128 nscoord rowB = mInitialOffsetB;
6129 nsPresContext* presContext = mTable->PresContext();
6130 for (uint32_t rgIdx = 0; rgIdx < mRowGroups.Length() && !done; rgIdx++) {
6131 nsTableRowGroupFrame* rgFrame = mRowGroups[rgIdx];
6132 for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
6133 rowFrame = rowFrame->GetNextRow()) {
6134 // get the row rect relative to the table rather than the row group
6135 nscoord rowBSize = rowFrame->BSize(mTableWM);
6136 if (haveIntersect) {
6137 // conservatively estimate the half border widths outside the row
6138 nscoord borderHalf = mTable->GetPrevInFlow()
6140 : presContext->DevPixelsToAppUnits(
6141 rowFrame->GetBStartBCBorderWidth() + 1);
6143 if (dirtyRect.BEnd(mTableWM) >= rowB - borderHalf) {
6144 nsTableRowFrame* fifRow =
6145 static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
6146 endRowIndex = fifRow->GetRowIndex();
6147 } else
6148 done = true;
6149 } else {
6150 // conservatively estimate the half border widths outside the row
6151 nscoord borderHalf = mTable->GetNextInFlow()
6153 : presContext->DevPixelsToAppUnits(
6154 rowFrame->GetBEndBCBorderWidth() + 1);
6155 if (rowB + rowBSize + borderHalf >= dirtyRect.BStart(mTableWM)) {
6156 mStartRg = rgFrame;
6157 mStartRow = rowFrame;
6158 nsTableRowFrame* fifRow =
6159 static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
6160 startRowIndex = endRowIndex = fifRow->GetRowIndex();
6161 haveIntersect = true;
6162 } else {
6163 mInitialOffsetB += rowBSize;
6166 rowB += rowBSize;
6169 mNextOffsetB = mInitialOffsetB;
6171 // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag
6172 // XXX but I don't understand it, so not changing it for now
6173 // table wrapper borders overflow the table, so the table might be
6174 // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set
6175 // on the table
6176 if (!haveIntersect) return false;
6177 // find startColIndex, endColIndex, startColX
6178 haveIntersect = false;
6179 if (0 == mNumTableCols) return false;
6181 LogicalMargin childAreaOffset = mTable->GetChildAreaOffset(mTableWM, nullptr);
6183 // inline position of first col in damage area
6184 mInitialOffsetI = childAreaOffset.IStart(mTableWM);
6186 nscoord x = 0;
6187 int32_t colIdx;
6188 for (colIdx = 0; colIdx != mNumTableCols; colIdx++) {
6189 nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colIdx);
6190 if (!colFrame) ABORT1(false);
6191 // get the col rect relative to the table rather than the col group
6192 nscoord colISize = colFrame->ISize(mTableWM);
6193 if (haveIntersect) {
6194 // conservatively estimate the iStart half border width outside the col
6195 nscoord iStartBorderHalf = presContext->DevPixelsToAppUnits(
6196 colFrame->GetIStartBorderWidth() + 1);
6197 if (dirtyRect.IEnd(mTableWM) >= x - iStartBorderHalf) {
6198 endColIndex = colIdx;
6199 } else
6200 break;
6201 } else {
6202 // conservatively estimate the iEnd half border width outside the col
6203 nscoord iEndBorderHalf =
6204 presContext->DevPixelsToAppUnits(colFrame->GetIEndBorderWidth() + 1);
6205 if (x + colISize + iEndBorderHalf >= dirtyRect.IStart(mTableWM)) {
6206 startColIndex = endColIndex = colIdx;
6207 haveIntersect = true;
6208 } else {
6209 mInitialOffsetI += colISize;
6212 x += colISize;
6214 if (!haveIntersect) return false;
6215 mDamageArea =
6216 TableArea(startColIndex, startRowIndex,
6217 1 + DeprecatedAbs<int32_t>(endColIndex - startColIndex),
6218 1 + endRowIndex - startRowIndex);
6220 Reset();
6221 mBlockDirInfo = MakeUnique<BCBlockDirSeg[]>(mDamageArea.ColCount() + 1);
6222 return true;
6225 void BCPaintBorderIterator::Reset() {
6226 mAtEnd = true; // gets reset when First() is called
6227 mRg = mStartRg;
6228 mPrevRow = nullptr;
6229 mRow = mStartRow;
6230 mRowIndex = 0;
6231 mColIndex = 0;
6232 mRgIndex = -1;
6233 mPrevCell = nullptr;
6234 mCell = nullptr;
6235 mPrevCellData = nullptr;
6236 mCellData = nullptr;
6237 mBCData = nullptr;
6238 ResetVerInfo();
6242 * Set the iterator data to a new cellmap coordinate
6243 * @param aRowIndex - the row index
6244 * @param aColIndex - the col index
6246 void BCPaintBorderIterator::SetNewData(int32_t aY, int32_t aX) {
6247 if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0();
6249 mColIndex = aX;
6250 mRowIndex = aY;
6251 mPrevCellData = mCellData;
6252 if (IsTableIEndMost() && IsTableBEndMost()) {
6253 mCell = nullptr;
6254 mBCData = &mTableCellMap->mBCInfo->mBEndIEndCorner;
6255 } else if (IsTableIEndMost()) {
6256 mCellData = nullptr;
6257 mBCData = &mTableCellMap->mBCInfo->mIEndBorders.ElementAt(aY);
6258 } else if (IsTableBEndMost()) {
6259 mCellData = nullptr;
6260 mBCData = &mTableCellMap->mBCInfo->mBEndBorders.ElementAt(aX);
6261 } else {
6262 if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) {
6263 mBCData = nullptr;
6264 mCellData = (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex]
6265 .SafeElementAt(mColIndex);
6266 if (mCellData) {
6267 mBCData = &mCellData->mData;
6268 if (!mCellData->IsOrig()) {
6269 if (mCellData->IsRowSpan()) {
6270 aY -= mCellData->GetRowSpanOffset();
6272 if (mCellData->IsColSpan()) {
6273 aX -= mCellData->GetColSpanOffset();
6275 if ((aX >= 0) && (aY >= 0)) {
6276 mCellData =
6277 (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX];
6280 if (mCellData->IsOrig()) {
6281 mPrevCell = mCell;
6282 mCell = mCellData->GetCellFrame();
6290 * Set the iterator to a new row
6291 * @param aRow - the new row frame, if null the iterator will advance to the
6292 * next row
6294 bool BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow) {
6295 mPrevRow = mRow;
6296 mRow = (aRow) ? aRow : mRow->GetNextRow();
6297 if (mRow) {
6298 mIsNewRow = true;
6299 mRowIndex = mRow->GetRowIndex();
6300 mColIndex = mDamageArea.StartCol();
6301 mPrevInlineSegBSize = 0;
6302 if (mIsRepeatedHeader) {
6303 mRepeatedHeaderRowIndex = mRowIndex;
6305 } else {
6306 mAtEnd = true;
6308 return !mAtEnd;
6312 * Advance the iterator to the next row group
6314 bool BCPaintBorderIterator::SetNewRowGroup() {
6315 mRgIndex++;
6317 mIsRepeatedHeader = false;
6318 mIsRepeatedFooter = false;
6320 NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds");
6321 if (uint32_t(mRgIndex) < mRowGroups.Length()) {
6322 mPrevRg = mRg;
6323 mRg = mRowGroups[mRgIndex];
6324 nsTableRowGroupFrame* fifRg =
6325 static_cast<nsTableRowGroupFrame*>(mRg->FirstInFlow());
6326 mFifRgFirstRowIndex = fifRg->GetStartRowIndex();
6327 mRgFirstRowIndex = mRg->GetStartRowIndex();
6328 mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1;
6330 if (SetNewRow(mRg->GetFirstRow())) {
6331 mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr);
6332 if (!mCellMap) ABORT1(false);
6334 if (mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) {
6335 // if mRowGroup doesn't have a prev in flow, then it may be a repeated
6336 // header or footer
6337 const nsStyleDisplay* display = mRg->StyleDisplay();
6338 if (mRowIndex == mDamageArea.StartRow()) {
6339 mIsRepeatedHeader =
6340 (mozilla::StyleDisplay::TableHeaderGroup == display->mDisplay);
6341 } else {
6342 mIsRepeatedFooter =
6343 (mozilla::StyleDisplay::TableFooterGroup == display->mDisplay);
6346 } else {
6347 mAtEnd = true;
6349 return !mAtEnd;
6353 * Move the iterator to the first position in the damageArea
6355 void BCPaintBorderIterator::First() {
6356 if (!mTable || mDamageArea.StartCol() >= mNumTableCols ||
6357 mDamageArea.StartRow() >= mNumTableRows)
6358 ABORT0();
6360 mAtEnd = false;
6362 uint32_t numRowGroups = mRowGroups.Length();
6363 for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) {
6364 nsTableRowGroupFrame* rowG = mRowGroups[rgY];
6365 int32_t start = rowG->GetStartRowIndex();
6366 int32_t end = start + rowG->GetRowCount() - 1;
6367 if (mDamageArea.StartRow() >= start && mDamageArea.StartRow() <= end) {
6368 mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex
6369 if (SetNewRowGroup()) {
6370 while (mRowIndex < mDamageArea.StartRow() && !mAtEnd) {
6371 SetNewRow();
6373 if (!mAtEnd) {
6374 SetNewData(mDamageArea.StartRow(), mDamageArea.StartCol());
6377 return;
6380 mAtEnd = true;
6384 * Advance the iterator to the next position
6386 void BCPaintBorderIterator::Next() {
6387 if (mAtEnd) ABORT0();
6388 mIsNewRow = false;
6390 mColIndex++;
6391 if (mColIndex > mDamageArea.EndCol()) {
6392 mRowIndex++;
6393 if (mRowIndex == mDamageArea.EndRow()) {
6394 mColIndex = mDamageArea.StartCol();
6395 } else if (mRowIndex < mDamageArea.EndRow()) {
6396 if (mRowIndex <= mRgLastRowIndex) {
6397 SetNewRow();
6398 } else {
6399 SetNewRowGroup();
6401 } else {
6402 mAtEnd = true;
6405 if (!mAtEnd) {
6406 SetNewData(mRowIndex, mColIndex);
6410 // XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine
6411 // them
6412 // XXX Update terminology from physical to logical
6413 /** Compute the vertical offset of a vertical border segment
6414 * @param aCornerOwnerSide - which side owns the corner
6415 * @param aCornerSubWidth - how wide is the nonwinning side of the corner
6416 * @param aHorWidth - how wide is the horizontal edge of the corner
6417 * @param aIsStartOfSeg - does this corner start a new segment
6418 * @param aIsBevel - is this corner beveled
6419 * @return - offset in twips
6421 static nscoord CalcVerCornerOffset(nsPresContext* aPresContext,
6422 LogicalSide aCornerOwnerSide,
6423 BCPixelSize aCornerSubWidth,
6424 BCPixelSize aHorWidth, bool aIsStartOfSeg,
6425 bool aIsBevel) {
6426 nscoord offset = 0;
6427 // XXX These should be replaced with appropriate side-specific macros (which?)
6428 BCPixelSize smallHalf, largeHalf;
6429 if (IsBlock(aCornerOwnerSide)) {
6430 DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
6431 if (aIsBevel) {
6432 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6433 } else {
6434 offset =
6435 (eLogicalSideBStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
6437 } else {
6438 DivideBCBorderSize(aHorWidth, smallHalf, largeHalf);
6439 if (aIsBevel) {
6440 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6441 } else {
6442 offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
6445 return aPresContext->DevPixelsToAppUnits(offset);
6448 /** Compute the horizontal offset of a horizontal border segment
6449 * @param aCornerOwnerSide - which side owns the corner
6450 * @param aCornerSubWidth - how wide is the nonwinning side of the corner
6451 * @param aVerWidth - how wide is the vertical edge of the corner
6452 * @param aIsStartOfSeg - does this corner start a new segment
6453 * @param aIsBevel - is this corner beveled
6454 * @return - offset in twips
6456 static nscoord CalcHorCornerOffset(nsPresContext* aPresContext,
6457 LogicalSide aCornerOwnerSide,
6458 BCPixelSize aCornerSubWidth,
6459 BCPixelSize aVerWidth, bool aIsStartOfSeg,
6460 bool aIsBevel) {
6461 nscoord offset = 0;
6462 // XXX These should be replaced with appropriate side-specific macros (which?)
6463 BCPixelSize smallHalf, largeHalf;
6464 if (IsInline(aCornerOwnerSide)) {
6465 DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
6466 if (aIsBevel) {
6467 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6468 } else {
6469 offset =
6470 (eLogicalSideIStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
6472 } else {
6473 DivideBCBorderSize(aVerWidth, smallHalf, largeHalf);
6474 if (aIsBevel) {
6475 offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
6476 } else {
6477 offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
6480 return aPresContext->DevPixelsToAppUnits(offset);
6483 BCBlockDirSeg::BCBlockDirSeg()
6484 : mFirstRowGroup(nullptr),
6485 mFirstRow(nullptr),
6486 mBEndInlineSegBSize(0),
6487 mBEndOffset(0),
6488 mIsBEndBevel(false) {
6489 mCol = nullptr;
6490 mFirstCell = mLastCell = mAjaCell = nullptr;
6491 mOffsetI = mOffsetB = mLength = mWidth = mBStartBevelOffset = 0;
6492 mBStartBevelSide = eLogicalSideBStart;
6493 mOwner = eCellOwner;
6497 * Start a new block-direction segment
6498 * @param aIter - iterator containing the structural information
6499 * @param aBorderOwner - determines the border style
6500 * @param aBlockSegISize - the width of segment in pixel
6501 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6502 * corner at the start
6504 void BCBlockDirSeg::Start(BCPaintBorderIterator& aIter,
6505 BCBorderOwner aBorderOwner,
6506 BCPixelSize aBlockSegISize,
6507 BCPixelSize aInlineSegBSize,
6508 Maybe<nscoord> aEmptyRowEndBSize) {
6509 LogicalSide ownerSide = eLogicalSideBStart;
6510 bool bevel = false;
6512 nscoord cornerSubWidth =
6513 (aIter.mBCData) ? aIter.mBCData->GetCorner(ownerSide, bevel) : 0;
6515 bool bStartBevel = (aBlockSegISize > 0) ? bevel : false;
6516 BCPixelSize maxInlineSegBSize =
6517 std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
6518 nsPresContext* presContext = aIter.mTable->PresContext();
6519 nscoord offset = CalcVerCornerOffset(presContext, ownerSide, cornerSubWidth,
6520 maxInlineSegBSize, true, bStartBevel);
6522 mBStartBevelOffset =
6523 bStartBevel ? presContext->DevPixelsToAppUnits(maxInlineSegBSize) : 0;
6524 // XXX this assumes that only corners where 2 segments join can be beveled
6525 mBStartBevelSide =
6526 (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart;
6527 if (aEmptyRowEndBSize && *aEmptyRowEndBSize < offset) {
6528 // This segment is starting from an empty row. This will require the the
6529 // starting segment to overlap with the previously drawn segment, unless the
6530 // empty row's size clears the overlap.
6531 mOffsetB += *aEmptyRowEndBSize;
6532 } else {
6533 mOffsetB += offset;
6535 mLength = -offset;
6536 mWidth = aBlockSegISize;
6537 mOwner = aBorderOwner;
6538 mFirstCell = aIter.mCell;
6539 mFirstRowGroup = aIter.mRg;
6540 mFirstRow = aIter.mRow;
6541 if (aIter.GetRelativeColIndex() > 0) {
6542 mAjaCell = aIter.mBlockDirInfo[aIter.GetRelativeColIndex() - 1].mLastCell;
6547 * Initialize the block-dir segments with information that will persist for any
6548 * block-dir segment in this column
6549 * @param aIter - iterator containing the structural information
6551 void BCBlockDirSeg::Initialize(BCPaintBorderIterator& aIter) {
6552 int32_t relColIndex = aIter.GetRelativeColIndex();
6553 mCol = aIter.IsTableIEndMost()
6554 ? aIter.mBlockDirInfo[relColIndex - 1].mCol
6555 : aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex);
6556 if (!mCol) ABORT0();
6557 if (0 == relColIndex) {
6558 mOffsetI = aIter.mInitialOffsetI;
6560 // set mOffsetI for the next column
6561 if (!aIter.IsDamageAreaIEndMost()) {
6562 aIter.mBlockDirInfo[relColIndex + 1].mOffsetI =
6563 mOffsetI + mCol->ISize(aIter.mTableWM);
6565 mOffsetB = aIter.mInitialOffsetB;
6566 mLastCell = aIter.mCell;
6570 * Compute the offsets for the bEnd corner of a block-dir segment
6571 * @param aIter - iterator containing the structural information
6572 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6573 * corner at the start
6575 void BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator& aIter,
6576 BCPixelSize aInlineSegBSize) {
6577 LogicalSide ownerSide = eLogicalSideBStart;
6578 nscoord cornerSubWidth = 0;
6579 bool bevel = false;
6580 if (aIter.mBCData) {
6581 cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
6583 mIsBEndBevel = (mWidth > 0) ? bevel : false;
6584 mBEndInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
6585 mBEndOffset = CalcVerCornerOffset(aIter.mTable->PresContext(), ownerSide,
6586 cornerSubWidth, mBEndInlineSegBSize, false,
6587 mIsBEndBevel);
6588 mLength += mBEndOffset;
6591 Maybe<BCBorderParameters> BCBlockDirSeg::BuildBorderParameters(
6592 BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize) {
6593 BCBorderParameters result;
6595 // get the border style, color and paint the segment
6596 LogicalSide side =
6597 aIter.IsDamageAreaIEndMost() ? eLogicalSideIEnd : eLogicalSideIStart;
6598 int32_t relColIndex = aIter.GetRelativeColIndex();
6599 nsTableColFrame* col = mCol;
6600 if (!col) ABORT1(Nothing());
6601 nsTableCellFrame* cell = mFirstCell; // ???
6602 nsIFrame* owner = nullptr;
6603 result.mBorderStyle = StyleBorderStyle::Solid;
6604 result.mBorderColor = 0xFFFFFFFF;
6605 result.mBackfaceIsVisible = true;
6607 // All the tables frames have the same presContext, so we just use any one
6608 // that exists here:
6609 nsPresContext* presContext = aIter.mTable->PresContext();
6610 result.mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
6612 switch (mOwner) {
6613 case eTableOwner:
6614 owner = aIter.mTable;
6615 break;
6616 case eAjaColGroupOwner:
6617 side = eLogicalSideIEnd;
6618 if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
6619 col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
6621 [[fallthrough]];
6622 case eColGroupOwner:
6623 if (col) {
6624 owner = col->GetParent();
6626 break;
6627 case eAjaColOwner:
6628 side = eLogicalSideIEnd;
6629 if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
6630 col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
6632 [[fallthrough]];
6633 case eColOwner:
6634 owner = col;
6635 break;
6636 case eAjaRowGroupOwner:
6637 NS_ERROR("a neighboring rowgroup can never own a vertical border");
6638 [[fallthrough]];
6639 case eRowGroupOwner:
6640 NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
6641 "row group can own border only at table edge");
6642 owner = mFirstRowGroup;
6643 break;
6644 case eAjaRowOwner:
6645 NS_ERROR("program error");
6646 [[fallthrough]];
6647 case eRowOwner:
6648 NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
6649 "row can own border only at table edge");
6650 owner = mFirstRow;
6651 break;
6652 case eAjaCellOwner:
6653 side = eLogicalSideIEnd;
6654 cell = mAjaCell;
6655 [[fallthrough]];
6656 case eCellOwner:
6657 owner = cell;
6658 break;
6660 if (owner) {
6661 ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle,
6662 &result.mBorderColor);
6663 result.mBackfaceIsVisible = !owner->BackfaceIsHidden();
6665 BCPixelSize smallHalf, largeHalf;
6666 DivideBCBorderSize(mWidth, smallHalf, largeHalf);
6667 LogicalRect segRect(
6668 aIter.mTableWM, mOffsetI - presContext->DevPixelsToAppUnits(largeHalf),
6669 mOffsetB, presContext->DevPixelsToAppUnits(mWidth), mLength);
6670 nscoord bEndBevelOffset =
6671 (mIsBEndBevel) ? presContext->DevPixelsToAppUnits(mBEndInlineSegBSize)
6672 : 0;
6673 LogicalSide bEndBevelSide =
6674 (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart;
6676 // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
6678 result.mBorderRect =
6679 segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize());
6680 // XXX For reversed vertical writing-modes (with direction:rtl), we need to
6681 // invert physicalRect's y-position here, with respect to the table.
6682 // However, it's not worth fixing the border positions here until the
6683 // ordering of the table columns themselves is also fixed (bug 1180528).
6685 result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide);
6686 result.mEndBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide);
6687 result.mStartBevelOffset = mBStartBevelOffset;
6688 result.mEndBevelOffset = bEndBevelOffset;
6689 // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal)
6690 // border segment need to be swapped because DrawTableBorderSegment will
6691 // apply the 'start' bevel at the left edge, and 'end' at the right.
6692 // (Note: In this case, startBevelSide/endBevelSide will usually both be
6693 // "top" or "bottom". DrawTableBorderSegment works purely with physical
6694 // coordinates, so it expects startBevelOffset to be the indentation-from-
6695 // the-left for the "start" (left) end of the border-segment, and
6696 // endBevelOffset is the indentation-from-the-right for the "end" (right)
6697 // end of the border-segment. We've got them reversed, since our block dir
6698 // is RTL, so we have to swap them here.)
6699 if (aIter.mTableWM.IsVerticalRL()) {
6700 std::swap(result.mStartBevelSide, result.mEndBevelSide);
6701 std::swap(result.mStartBevelOffset, result.mEndBevelOffset);
6704 return Some(result);
6708 * Paint the block-dir segment
6709 * @param aIter - iterator containing the structural information
6710 * @param aDrawTarget - the draw target
6711 * @param aInlineSegBSize - the width of the inline-dir segment joining the
6712 * corner at the start
6714 void BCBlockDirSeg::Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget,
6715 BCPixelSize aInlineSegBSize) {
6716 Maybe<BCBorderParameters> param =
6717 BuildBorderParameters(aIter, aInlineSegBSize);
6718 if (param.isNothing()) {
6719 return;
6722 nsCSSRendering::DrawTableBorderSegment(
6723 aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect,
6724 param->mAppUnitsPerDevPixel, param->mStartBevelSide,
6725 param->mStartBevelOffset, param->mEndBevelSide, param->mEndBevelOffset);
6728 // Pushes a border bevel triangle and substracts the relevant rectangle from
6729 // aRect, which, after all the bevels, will end up being a solid segment rect.
6730 static void AdjustAndPushBevel(wr::DisplayListBuilder& aBuilder,
6731 wr::LayoutRect& aRect, nscolor aColor,
6732 const nsCSSRendering::Bevel& aBevel,
6733 int32_t aAppUnitsPerDevPixel,
6734 bool aBackfaceIsVisible, bool aIsStart) {
6735 if (!aBevel.mOffset) {
6736 return;
6739 const auto kTransparent = wr::ToColorF(gfx::DeviceColor(0., 0., 0., 0.));
6740 const bool horizontal =
6741 aBevel.mSide == eSideTop || aBevel.mSide == eSideBottom;
6743 // Crappy CSS triangle as known by every web developer ever :)
6744 Float offset = NSAppUnitsToFloatPixels(aBevel.mOffset, aAppUnitsPerDevPixel);
6745 wr::LayoutRect bevelRect = aRect;
6746 wr::BorderSide bevelBorder[4];
6747 for (const auto i : mozilla::AllPhysicalSides()) {
6748 bevelBorder[i] =
6749 wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid);
6752 // We're creating a half-transparent triangle using the border primitive.
6754 // Classic web-dev trick, with a gotcha: we use a single corner to avoid
6755 // seams and rounding errors.
6757 // Classic web-dev trick :P
6758 auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0);
6759 bevelBorder[aBevel.mSide].color = kTransparent;
6760 if (aIsStart) {
6761 if (horizontal) {
6762 bevelBorder[eSideLeft].color = kTransparent;
6763 borderWidths.left = offset;
6764 } else {
6765 bevelBorder[eSideTop].color = kTransparent;
6766 borderWidths.top = offset;
6768 } else {
6769 if (horizontal) {
6770 bevelBorder[eSideRight].color = kTransparent;
6771 borderWidths.right = offset;
6772 } else {
6773 bevelBorder[eSideBottom].color = kTransparent;
6774 borderWidths.bottom = offset;
6778 if (horizontal) {
6779 if (aIsStart) {
6780 aRect.min.x += offset;
6781 aRect.max.x += offset;
6782 } else {
6783 bevelRect.min.x += aRect.width() - offset;
6784 bevelRect.max.x += aRect.width() - offset;
6786 aRect.max.x -= offset;
6787 bevelRect.max.y = bevelRect.min.y + aRect.height();
6788 bevelRect.max.x = bevelRect.min.x + offset;
6789 if (aBevel.mSide == eSideTop) {
6790 borderWidths.bottom = aRect.height();
6791 } else {
6792 borderWidths.top = aRect.height();
6794 } else {
6795 if (aIsStart) {
6796 aRect.min.y += offset;
6797 aRect.max.y += offset;
6798 } else {
6799 bevelRect.min.y += aRect.height() - offset;
6800 bevelRect.max.y += aRect.height() - offset;
6802 aRect.max.y -= offset;
6803 bevelRect.max.x = bevelRect.min.x + aRect.width();
6804 bevelRect.max.y = bevelRect.min.y + offset;
6805 if (aBevel.mSide == eSideLeft) {
6806 borderWidths.right = aRect.width();
6807 } else {
6808 borderWidths.left = aRect.width();
6812 Range<const wr::BorderSide> wrsides(bevelBorder, 4);
6813 // It's important to _not_ anti-alias the bevel, because otherwise we wouldn't
6814 // be able bevel to sides of the same color without bleeding in the middle.
6815 aBuilder.PushBorder(bevelRect, bevelRect, aBackfaceIsVisible, borderWidths,
6816 wrsides, wr::EmptyBorderRadius(),
6817 wr::AntialiasBorder::No);
6820 static void CreateWRCommandsForBeveledBorder(
6821 const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder,
6822 const layers::StackingContextHelper& aSc, const nsPoint& aOffset) {
6823 MOZ_ASSERT(aBorderParams.NeedToBevel());
6825 AutoTArray<nsCSSRendering::SolidBeveledBorderSegment, 3> segments;
6826 nsCSSRendering::GetTableBorderSolidSegments(
6827 segments, aBorderParams.mBorderStyle, aBorderParams.mBorderColor,
6828 aBorderParams.mBorderRect, aBorderParams.mAppUnitsPerDevPixel,
6829 aBorderParams.mStartBevelSide, aBorderParams.mStartBevelOffset,
6830 aBorderParams.mEndBevelSide, aBorderParams.mEndBevelOffset);
6832 for (const auto& segment : segments) {
6833 auto rect = LayoutDeviceRect::FromUnknownRect(NSRectToRect(
6834 segment.mRect + aOffset, aBorderParams.mAppUnitsPerDevPixel));
6835 auto r = wr::ToLayoutRect(rect);
6836 auto color = wr::ToColorF(ToDeviceColor(segment.mColor));
6838 // Adjust for the start bevel if needed.
6839 AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mStartBevel,
6840 aBorderParams.mAppUnitsPerDevPixel,
6841 aBorderParams.mBackfaceIsVisible, true);
6843 AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mEndBevel,
6844 aBorderParams.mAppUnitsPerDevPixel,
6845 aBorderParams.mBackfaceIsVisible, false);
6847 aBuilder.PushRect(r, r, aBorderParams.mBackfaceIsVisible, false, false,
6848 color);
6852 static void CreateWRCommandsForBorderSegment(
6853 const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder,
6854 const layers::StackingContextHelper& aSc, const nsPoint& aOffset) {
6855 if (aBorderParams.NeedToBevel()) {
6856 CreateWRCommandsForBeveledBorder(aBorderParams, aBuilder, aSc, aOffset);
6857 return;
6860 auto borderRect = LayoutDeviceRect::FromUnknownRect(NSRectToRect(
6861 aBorderParams.mBorderRect + aOffset, aBorderParams.mAppUnitsPerDevPixel));
6863 wr::LayoutRect r = wr::ToLayoutRect(borderRect);
6864 wr::BorderSide wrSide[4];
6865 for (const auto i : mozilla::AllPhysicalSides()) {
6866 wrSide[i] = wr::ToBorderSide(ToDeviceColor(aBorderParams.mBorderColor),
6867 StyleBorderStyle::None);
6869 const bool horizontal = aBorderParams.mStartBevelSide == eSideTop ||
6870 aBorderParams.mStartBevelSide == eSideBottom;
6871 auto borderWidth = horizontal ? r.height() : r.width();
6873 // All border style is set to none except left side. So setting the widths of
6874 // each side to width of rect is fine.
6875 auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0);
6877 wrSide[horizontal ? eSideTop : eSideLeft] = wr::ToBorderSide(
6878 ToDeviceColor(aBorderParams.mBorderColor), aBorderParams.mBorderStyle);
6880 if (horizontal) {
6881 borderWidths.top = borderWidth;
6882 } else {
6883 borderWidths.left = borderWidth;
6886 Range<const wr::BorderSide> wrsides(wrSide, 4);
6887 aBuilder.PushBorder(r, r, aBorderParams.mBackfaceIsVisible, borderWidths,
6888 wrsides, wr::EmptyBorderRadius());
6891 void BCBlockDirSeg::CreateWebRenderCommands(
6892 BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize,
6893 wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc,
6894 const nsPoint& aOffset) {
6895 Maybe<BCBorderParameters> param =
6896 BuildBorderParameters(aIter, aInlineSegBSize);
6897 if (param.isNothing()) {
6898 return;
6901 CreateWRCommandsForBorderSegment(*param, aBuilder, aSc, aOffset);
6905 * Advance the start point of a segment
6907 void BCBlockDirSeg::AdvanceOffsetB() { mOffsetB += mLength - mBEndOffset; }
6910 * Accumulate the current segment
6912 void BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) {
6913 mLastCell = aIter.mCell;
6914 mLength += aIter.mRow->BSize(aIter.mTableWM);
6917 BCInlineDirSeg::BCInlineDirSeg()
6918 : mIsIEndBevel(false),
6919 mIEndBevelOffset(0),
6920 mIEndBevelSide(eLogicalSideBStart),
6921 mEndOffset(0),
6922 mOwner(eTableOwner) {
6923 mOffsetI = mOffsetB = mLength = mWidth = mIStartBevelOffset = 0;
6924 mIStartBevelSide = eLogicalSideBStart;
6925 mFirstCell = mAjaCell = nullptr;
6928 /** Initialize an inline-dir border segment for painting
6929 * @param aIter - iterator storing the current and adjacent frames
6930 * @param aBorderOwner - which frame owns the border
6931 * @param aBEndBlockSegISize - block-dir segment width coming from up
6932 * @param aInlineSegBSize - the thickness of the segment
6933 + */
6934 void BCInlineDirSeg::Start(BCPaintBorderIterator& aIter,
6935 BCBorderOwner aBorderOwner,
6936 BCPixelSize aBEndBlockSegISize,
6937 BCPixelSize aInlineSegBSize) {
6938 LogicalSide cornerOwnerSide = eLogicalSideBStart;
6939 bool bevel = false;
6941 mOwner = aBorderOwner;
6942 nscoord cornerSubWidth =
6943 (aIter.mBCData) ? aIter.mBCData->GetCorner(cornerOwnerSide, bevel) : 0;
6945 bool iStartBevel = (aInlineSegBSize > 0) ? bevel : false;
6946 int32_t relColIndex = aIter.GetRelativeColIndex();
6947 nscoord maxBlockSegISize =
6948 std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aBEndBlockSegISize);
6949 nscoord offset =
6950 CalcHorCornerOffset(aIter.mTable->PresContext(), cornerOwnerSide,
6951 cornerSubWidth, maxBlockSegISize, true, iStartBevel);
6952 mIStartBevelOffset =
6953 (iStartBevel && (aInlineSegBSize > 0)) ? maxBlockSegISize : 0;
6954 // XXX this assumes that only corners where 2 segments join can be beveled
6955 mIStartBevelSide =
6956 (aBEndBlockSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart;
6957 mOffsetI += offset;
6958 mLength = -offset;
6959 mWidth = aInlineSegBSize;
6960 mFirstCell = aIter.mCell;
6961 mAjaCell = (aIter.IsDamageAreaBStartMost())
6962 ? nullptr
6963 : aIter.mBlockDirInfo[relColIndex].mLastCell;
6967 * Compute the offsets for the iEnd corner of an inline-dir segment
6968 * @param aIter - iterator containing the structural information
6969 * @param aIStartSegISize - the iSize of the block-dir segment joining the
6970 * corner at the start
6972 void BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator& aIter,
6973 BCPixelSize aIStartSegISize) {
6974 LogicalSide ownerSide = eLogicalSideBStart;
6975 nscoord cornerSubWidth = 0;
6976 bool bevel = false;
6977 if (aIter.mBCData) {
6978 cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
6981 mIsIEndBevel = (mWidth > 0) ? bevel : 0;
6982 int32_t relColIndex = aIter.GetRelativeColIndex();
6983 nscoord verWidth =
6984 std::max(aIter.mBlockDirInfo[relColIndex].mWidth, aIStartSegISize);
6985 nsPresContext* presContext = aIter.mTable->PresContext();
6986 mEndOffset = CalcHorCornerOffset(presContext, ownerSide, cornerSubWidth,
6987 verWidth, false, mIsIEndBevel);
6988 mLength += mEndOffset;
6989 mIEndBevelOffset =
6990 (mIsIEndBevel) ? presContext->DevPixelsToAppUnits(verWidth) : 0;
6991 mIEndBevelSide =
6992 (aIStartSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart;
6995 Maybe<BCBorderParameters> BCInlineDirSeg::BuildBorderParameters(
6996 BCPaintBorderIterator& aIter) {
6997 BCBorderParameters result;
6999 // get the border style, color and paint the segment
7000 LogicalSide side =
7001 aIter.IsDamageAreaBEndMost() ? eLogicalSideBEnd : eLogicalSideBStart;
7002 nsIFrame* rg = aIter.mRg;
7003 if (!rg) ABORT1(Nothing());
7004 nsIFrame* row = aIter.mRow;
7005 if (!row) ABORT1(Nothing());
7006 nsIFrame* cell = mFirstCell;
7007 nsIFrame* col;
7008 nsIFrame* owner = nullptr;
7009 result.mBackfaceIsVisible = true;
7011 // All the tables frames have the same presContext, so we just use any one
7012 // that exists here:
7013 nsPresContext* presContext = aIter.mTable->PresContext();
7014 result.mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
7016 result.mBorderStyle = StyleBorderStyle::Solid;
7017 result.mBorderColor = 0xFFFFFFFF;
7019 switch (mOwner) {
7020 case eTableOwner:
7021 owner = aIter.mTable;
7022 break;
7023 case eAjaColGroupOwner:
7024 NS_ERROR("neighboring colgroups can never own an inline-dir border");
7025 [[fallthrough]];
7026 case eColGroupOwner:
7027 NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
7028 "col group can own border only at the table edge");
7029 col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
7030 if (!col) ABORT1(Nothing());
7031 owner = col->GetParent();
7032 break;
7033 case eAjaColOwner:
7034 NS_ERROR("neighboring column can never own an inline-dir border");
7035 [[fallthrough]];
7036 case eColOwner:
7037 NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
7038 "col can own border only at the table edge");
7039 owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
7040 break;
7041 case eAjaRowGroupOwner:
7042 side = eLogicalSideBEnd;
7043 rg = (aIter.IsTableBEndMost()) ? aIter.mRg : aIter.mPrevRg;
7044 [[fallthrough]];
7045 case eRowGroupOwner:
7046 owner = rg;
7047 break;
7048 case eAjaRowOwner:
7049 side = eLogicalSideBEnd;
7050 row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow;
7051 [[fallthrough]];
7052 case eRowOwner:
7053 owner = row;
7054 break;
7055 case eAjaCellOwner:
7056 side = eLogicalSideBEnd;
7057 // if this is null due to the damage area origin-y > 0, then the border
7058 // won't show up anyway
7059 cell = mAjaCell;
7060 [[fallthrough]];
7061 case eCellOwner:
7062 owner = cell;
7063 break;
7065 if (owner) {
7066 ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle,
7067 &result.mBorderColor);
7068 result.mBackfaceIsVisible = !owner->BackfaceIsHidden();
7070 BCPixelSize smallHalf, largeHalf;
7071 DivideBCBorderSize(mWidth, smallHalf, largeHalf);
7072 LogicalRect segRect(aIter.mTableWM, mOffsetI,
7073 mOffsetB - presContext->DevPixelsToAppUnits(largeHalf),
7074 mLength, presContext->DevPixelsToAppUnits(mWidth));
7076 // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
7077 result.mBorderRect =
7078 segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize());
7079 result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide);
7080 result.mEndBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide);
7081 result.mStartBevelOffset =
7082 presContext->DevPixelsToAppUnits(mIStartBevelOffset);
7083 result.mEndBevelOffset = mIEndBevelOffset;
7084 // With inline-RTL directionality, the 'start' and 'end' of the inline-dir
7085 // border segment need to be swapped because DrawTableBorderSegment will
7086 // apply the 'start' bevel physically at the left or top edge, and 'end' at
7087 // the right or bottom.
7088 // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal
7089 // writing mode, or "left" or "right" in vertical mode.
7090 // DrawTableBorderSegment works purely with physical coordinates, so it
7091 // expects startBevelOffset to be the indentation-from-the-left or top end
7092 // of the border-segment, and endBevelOffset is the indentation-from-the-
7093 // right or bottom end. If the writing mode is inline-RTL, our "start" and
7094 // "end" will be reversed from this physical-coord view, so we have to swap
7095 // them here.
7096 if (aIter.mTableWM.IsBidiRTL()) {
7097 std::swap(result.mStartBevelSide, result.mEndBevelSide);
7098 std::swap(result.mStartBevelOffset, result.mEndBevelOffset);
7101 return Some(result);
7105 * Paint the inline-dir segment
7106 * @param aIter - iterator containing the structural information
7107 * @param aDrawTarget - the draw target
7109 void BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter,
7110 DrawTarget& aDrawTarget) {
7111 Maybe<BCBorderParameters> param = BuildBorderParameters(aIter);
7112 if (param.isNothing()) {
7113 return;
7116 nsCSSRendering::DrawTableBorderSegment(
7117 aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect,
7118 param->mAppUnitsPerDevPixel, param->mStartBevelSide,
7119 param->mStartBevelOffset, param->mEndBevelSide, param->mEndBevelOffset);
7122 void BCInlineDirSeg::CreateWebRenderCommands(
7123 BCPaintBorderIterator& aIter, wr::DisplayListBuilder& aBuilder,
7124 const layers::StackingContextHelper& aSc, const nsPoint& aPt) {
7125 Maybe<BCBorderParameters> param = BuildBorderParameters(aIter);
7126 if (param.isNothing()) {
7127 return;
7130 CreateWRCommandsForBorderSegment(*param, aBuilder, aSc, aPt);
7134 * Advance the start point of a segment
7136 void BCInlineDirSeg::AdvanceOffsetI() { mOffsetI += (mLength - mEndOffset); }
7139 * Accumulate the current segment
7141 void BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) {
7142 mLength += aIter.mBlockDirInfo[aIter.GetRelativeColIndex()].mColWidth;
7146 * store the column width information while painting inline-dir segment
7148 void BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) {
7149 if (IsTableIEndMost()) {
7150 mBlockDirInfo[aIndex].mColWidth = mBlockDirInfo[aIndex - 1].mColWidth;
7151 } else {
7152 nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex);
7153 if (!col) ABORT0();
7154 mBlockDirInfo[aIndex].mColWidth = col->ISize(mTableWM);
7158 * Determine if a block-dir segment owns the corner
7160 bool BCPaintBorderIterator::BlockDirSegmentOwnsCorner() {
7161 LogicalSide cornerOwnerSide = eLogicalSideBStart;
7162 bool bevel = false;
7163 if (mBCData) {
7164 mBCData->GetCorner(cornerOwnerSide, bevel);
7166 // unitialized ownerside, bevel
7167 return (eLogicalSideBStart == cornerOwnerSide) ||
7168 (eLogicalSideBEnd == cornerOwnerSide);
7172 * Paint if necessary an inline-dir segment, otherwise accumulate it
7173 * @param aDrawTarget - the draw target
7175 void BCPaintBorderIterator::AccumulateOrDoActionInlineDirSegment(
7176 BCPaintBorderAction& aAction) {
7177 int32_t relColIndex = GetRelativeColIndex();
7178 // store the current col width if it hasn't been already
7179 if (mBlockDirInfo[relColIndex].mColWidth < 0) {
7180 StoreColumnWidth(relColIndex);
7183 BCBorderOwner borderOwner = eCellOwner;
7184 BCBorderOwner ignoreBorderOwner;
7185 bool isSegStart = true;
7186 bool ignoreSegStart;
7188 nscoord iStartSegISize =
7189 mBCData ? mBCData->GetIStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
7190 nscoord bStartSegBSize =
7191 mBCData ? mBCData->GetBStartEdge(borderOwner, isSegStart) : 0;
7193 if (mIsNewRow || (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) {
7194 // reset for every new row and on the bottom of the last row
7195 mInlineSeg.mOffsetB = mNextOffsetB;
7196 mNextOffsetB = mNextOffsetB + mRow->BSize(mTableWM);
7197 mInlineSeg.mOffsetI = mInitialOffsetI;
7198 mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
7201 if (!IsDamageAreaIStartMost() &&
7202 (isSegStart || IsDamageAreaIEndMost() || BlockDirSegmentOwnsCorner())) {
7203 // paint the previous seg or the current one if IsDamageAreaIEndMost()
7204 if (mInlineSeg.mLength > 0) {
7205 mInlineSeg.GetIEndCorner(*this, iStartSegISize);
7206 if (mInlineSeg.mWidth > 0) {
7207 if (aAction.mMode == BCPaintBorderAction::Mode::Paint) {
7208 mInlineSeg.Paint(*this, aAction.mPaintData.mDrawTarget);
7209 } else {
7210 MOZ_ASSERT(aAction.mMode ==
7211 BCPaintBorderAction::Mode::CreateWebRenderCommands);
7212 mInlineSeg.CreateWebRenderCommands(
7213 *this, aAction.mCreateWebRenderCommandsData.mBuilder,
7214 aAction.mCreateWebRenderCommandsData.mSc,
7215 aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame);
7218 mInlineSeg.AdvanceOffsetI();
7220 mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
7222 mInlineSeg.IncludeCurrentBorder(*this);
7223 mBlockDirInfo[relColIndex].mWidth = iStartSegISize;
7224 mBlockDirInfo[relColIndex].mLastCell = mCell;
7228 * Paint if necessary a block-dir segment, otherwise accumulate it
7229 * @param aDrawTarget - the draw target
7231 void BCPaintBorderIterator::AccumulateOrDoActionBlockDirSegment(
7232 BCPaintBorderAction& aAction) {
7233 BCBorderOwner borderOwner = eCellOwner;
7234 BCBorderOwner ignoreBorderOwner;
7235 bool isSegStart = true;
7236 bool ignoreSegStart;
7238 nscoord blockSegISize =
7239 mBCData ? mBCData->GetIStartEdge(borderOwner, isSegStart) : 0;
7240 nscoord inlineSegBSize =
7241 mBCData ? mBCData->GetBStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
7243 int32_t relColIndex = GetRelativeColIndex();
7244 BCBlockDirSeg& blockDirSeg = mBlockDirInfo[relColIndex];
7245 if (!blockDirSeg.mCol) { // on the first damaged row and the first segment in
7246 // the col
7247 blockDirSeg.Initialize(*this);
7248 blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize,
7249 Nothing{});
7252 if (!IsDamageAreaBStartMost() &&
7253 (isSegStart || IsDamageAreaBEndMost() || IsAfterRepeatedHeader() ||
7254 StartRepeatedFooter())) {
7255 Maybe<nscoord> emptyRowEndSize;
7256 // paint the previous seg or the current one if IsDamageAreaBEndMost()
7257 if (blockDirSeg.mLength > 0) {
7258 blockDirSeg.GetBEndCorner(*this, inlineSegBSize);
7259 if (blockDirSeg.mWidth > 0) {
7260 if (aAction.mMode == BCPaintBorderAction::Mode::Paint) {
7261 blockDirSeg.Paint(*this, aAction.mPaintData.mDrawTarget,
7262 inlineSegBSize);
7263 } else {
7264 MOZ_ASSERT(aAction.mMode ==
7265 BCPaintBorderAction::Mode::CreateWebRenderCommands);
7266 blockDirSeg.CreateWebRenderCommands(
7267 *this, inlineSegBSize,
7268 aAction.mCreateWebRenderCommandsData.mBuilder,
7269 aAction.mCreateWebRenderCommandsData.mSc,
7270 aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame);
7273 blockDirSeg.AdvanceOffsetB();
7274 if (mRow->PrincipalChildList().IsEmpty()) {
7275 emptyRowEndSize = Some(mRow->BSize(mTableWM));
7278 blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize,
7279 emptyRowEndSize);
7281 blockDirSeg.IncludeCurrentBorder(*this);
7282 mPrevInlineSegBSize = inlineSegBSize;
7286 * Reset the block-dir information cache
7288 void BCPaintBorderIterator::ResetVerInfo() {
7289 if (mBlockDirInfo) {
7290 memset(mBlockDirInfo.get(), 0,
7291 mDamageArea.ColCount() * sizeof(BCBlockDirSeg));
7292 // XXX reinitialize properly
7293 for (auto xIndex : IntegerRange(mDamageArea.ColCount())) {
7294 mBlockDirInfo[xIndex].mColWidth = -1;
7299 void nsTableFrame::IterateBCBorders(BCPaintBorderAction& aAction,
7300 const nsRect& aDirtyRect) {
7301 // We first transfer the aDirtyRect into cellmap coordinates to compute which
7302 // cell borders need to be painted
7303 BCPaintBorderIterator iter(this);
7304 if (!iter.SetDamageArea(aDirtyRect)) return;
7306 // XXX comment still has physical terminology
7307 // First, paint all of the vertical borders from top to bottom and left to
7308 // right as they become complete. They are painted first, since they are less
7309 // efficient to paint than horizontal segments. They were stored with as few
7310 // segments as possible (since horizontal borders are painted last and
7311 // possibly over them). For every cell in a row that fails in the damage are
7312 // we look up if the current border would start a new segment, if so we paint
7313 // the previously stored vertical segment and start a new segment. After
7314 // this we the now active segment with the current border. These
7315 // segments are stored in mBlockDirInfo to be used on the next row
7316 for (iter.First(); !iter.mAtEnd; iter.Next()) {
7317 iter.AccumulateOrDoActionBlockDirSegment(aAction);
7320 // Next, paint all of the inline-dir border segments from bStart to bEnd reuse
7321 // the mBlockDirInfo array to keep track of col widths and block-dir segments
7322 // for corner calculations
7323 iter.Reset();
7324 for (iter.First(); !iter.mAtEnd; iter.Next()) {
7325 iter.AccumulateOrDoActionInlineDirSegment(aAction);
7330 * Method to paint BCBorders, this does not use currently display lists although
7331 * it will do this in future
7332 * @param aDrawTarget - the rendering context
7333 * @param aDirtyRect - inside this rectangle the BC Borders will redrawn
7335 void nsTableFrame::PaintBCBorders(DrawTarget& aDrawTarget,
7336 const nsRect& aDirtyRect) {
7337 BCPaintBorderAction action(aDrawTarget);
7338 IterateBCBorders(action, aDirtyRect);
7341 void nsTableFrame::CreateWebRenderCommandsForBCBorders(
7342 wr::DisplayListBuilder& aBuilder,
7343 const mozilla::layers::StackingContextHelper& aSc,
7344 const nsRect& aVisibleRect, const nsPoint& aOffsetToReferenceFrame) {
7345 BCPaintBorderAction action(aBuilder, aSc, aOffsetToReferenceFrame);
7346 // We always draw whole table border for webrender. Passing the visible rect
7347 // dirty rect.
7348 IterateBCBorders(action, aVisibleRect - aOffsetToReferenceFrame);
7351 bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) {
7352 bool result = false;
7353 nsTableCellMap* cellMap = GetCellMap();
7354 MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated.");
7355 if (cellMap) {
7356 result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols);
7358 return result;
7361 bool nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) {
7362 bool result = false;
7363 nsTableCellMap* cellMap = GetCellMap();
7364 MOZ_ASSERT(cellMap, "bad call, cellMap not yet allocated.");
7365 if (cellMap) {
7366 result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols);
7368 return result;
7371 /* static */
7372 void nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame,
7373 const nsRect& aOrigRect,
7374 const nsRect& aOrigInkOverflow,
7375 bool aIsFirstReflow) {
7376 nsIFrame* parent = aFrame->GetParent();
7377 NS_ASSERTION(parent, "What happened here?");
7379 if (parent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
7380 // Don't bother; we'll invalidate the parent's overflow rect when
7381 // we finish reflowing it.
7382 return;
7385 // The part that looks at both the rect and the overflow rect is a
7386 // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent
7387 // description of its hackishness.
7389 // This doesn't really make sense now that we have DLBI.
7390 // This code can probably be simplified a fair bit.
7391 nsRect inkOverflow = aFrame->InkOverflowRect();
7392 if (aIsFirstReflow || aOrigRect.TopLeft() != aFrame->GetPosition() ||
7393 aOrigInkOverflow.TopLeft() != inkOverflow.TopLeft()) {
7394 // Invalidate the old and new overflow rects. Note that if the
7395 // frame moved, we can't just use aOrigInkOverflow, since it's in
7396 // coordinates relative to the old position. So invalidate via
7397 // aFrame's parent, and reposition that overflow rect to the right
7398 // place.
7399 // XXXbz this doesn't handle outlines, does it?
7400 aFrame->InvalidateFrame();
7401 parent->InvalidateFrameWithRect(aOrigInkOverflow + aOrigRect.TopLeft());
7402 } else if (aOrigRect.Size() != aFrame->GetSize() ||
7403 aOrigInkOverflow.Size() != inkOverflow.Size()) {
7404 aFrame->InvalidateFrameWithRect(aOrigInkOverflow);
7405 aFrame->InvalidateFrame();
7409 void nsTableFrame::AppendDirectlyOwnedAnonBoxes(
7410 nsTArray<OwnedAnonBox>& aResult) {
7411 nsIFrame* wrapper = GetParent();
7412 MOZ_ASSERT(wrapper->Style()->GetPseudoType() == PseudoStyleType::tableWrapper,
7413 "What happened to our parent?");
7414 aResult.AppendElement(
7415 OwnedAnonBox(wrapper, &UpdateStyleOfOwnedAnonBoxesForTableWrapper));
7418 /* static */
7419 void nsTableFrame::UpdateStyleOfOwnedAnonBoxesForTableWrapper(
7420 nsIFrame* aOwningFrame, nsIFrame* aWrapperFrame,
7421 ServoRestyleState& aRestyleState) {
7422 MOZ_ASSERT(
7423 aWrapperFrame->Style()->GetPseudoType() == PseudoStyleType::tableWrapper,
7424 "What happened to our parent?");
7426 RefPtr<ComputedStyle> newStyle =
7427 aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
7428 PseudoStyleType::tableWrapper, aOwningFrame->Style());
7430 // Figure out whether we have an actual change. It's important that we do
7431 // this, even though all the wrapper's changes are due to properties it
7432 // inherits from us, because it's possible that no one ever asked us for those
7433 // style structs and hence changes to them aren't reflected in
7434 // the handled changes at all.
7436 // Also note that extensions can add/remove stylesheets that change the styles
7437 // of anonymous boxes directly, so we need to handle that potential change
7438 // here.
7440 // NOTE(emilio): We can't use the ChangesHandledFor optimization (and we
7441 // assert against that), because the table wrapper is up in the frame tree
7442 // compared to the owner frame.
7443 uint32_t equalStructs; // Not used, actually.
7444 nsChangeHint wrapperHint =
7445 aWrapperFrame->Style()->CalcStyleDifference(*newStyle, &equalStructs);
7447 if (wrapperHint) {
7448 aRestyleState.ChangeList().AppendChange(
7449 aWrapperFrame, aWrapperFrame->GetContent(), wrapperHint);
7452 for (nsIFrame* cur = aWrapperFrame; cur; cur = cur->GetNextContinuation()) {
7453 cur->SetComputedStyle(newStyle);
7456 MOZ_ASSERT(!aWrapperFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES),
7457 "Wrapper frame doesn't have any anon boxes of its own!");
7460 namespace mozilla {
7462 nsRect nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder,
7463 bool* aSnap) const {
7464 *aSnap = false;
7465 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
7468 nsDisplayTableBackgroundSet::nsDisplayTableBackgroundSet(
7469 nsDisplayListBuilder* aBuilder, nsIFrame* aTable)
7470 : mBuilder(aBuilder),
7471 mColGroupBackgrounds(aBuilder),
7472 mColBackgrounds(aBuilder),
7473 mCurrentScrollParentId(aBuilder->GetCurrentScrollParentId()) {
7474 mPrevTableBackgroundSet = mBuilder->SetTableBackgroundSet(this);
7475 mozilla::DebugOnly<const nsIFrame*> reference =
7476 mBuilder->FindReferenceFrameFor(aTable, &mToReferenceFrame);
7477 MOZ_ASSERT(nsLayoutUtils::FindNearestCommonAncestorFrame(reference, aTable));
7478 mDirtyRect = mBuilder->GetDirtyRect();
7479 mCombinedTableClipChain =
7480 mBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder);
7481 mTableASR = mBuilder->CurrentActiveScrolledRoot();
7484 // A display item that draws all collapsed borders for a table.
7485 // At some point, we may want to find a nicer partitioning for dividing
7486 // border-collapse segments into their own display items.
7487 class nsDisplayTableBorderCollapse final : public nsDisplayTableItem {
7488 public:
7489 nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder,
7490 nsTableFrame* aFrame)
7491 : nsDisplayTableItem(aBuilder, aFrame) {
7492 MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse);
7494 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableBorderCollapse)
7496 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
7497 bool CreateWebRenderCommands(
7498 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
7499 const StackingContextHelper& aSc,
7500 layers::RenderRootStateManager* aManager,
7501 nsDisplayListBuilder* aDisplayListBuilder) override;
7502 NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE)
7505 void nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder,
7506 gfxContext* aCtx) {
7507 nsPoint pt = ToReferenceFrame();
7508 DrawTarget* drawTarget = aCtx->GetDrawTarget();
7510 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
7511 pt, mFrame->PresContext()->AppUnitsPerDevPixel());
7513 // XXX we should probably get rid of this translation at some stage
7514 // But that would mean modifying PaintBCBorders, ugh
7515 AutoRestoreTransform autoRestoreTransform(drawTarget);
7516 drawTarget->SetTransform(
7517 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
7519 static_cast<nsTableFrame*>(mFrame)->PaintBCBorders(
7520 *drawTarget, GetPaintRect(aBuilder, aCtx) - pt);
7523 bool nsDisplayTableBorderCollapse::CreateWebRenderCommands(
7524 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
7525 const StackingContextHelper& aSc,
7526 mozilla::layers::RenderRootStateManager* aManager,
7527 nsDisplayListBuilder* aDisplayListBuilder) {
7528 bool dummy;
7529 static_cast<nsTableFrame*>(mFrame)->CreateWebRenderCommandsForBCBorders(
7530 aBuilder, aSc, GetBounds(aDisplayListBuilder, &dummy),
7531 ToReferenceFrame());
7532 return true;
7535 } // namespace mozilla