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