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