Bug 1763869 [wpt PR 33577] - Fix adb command to find webview package, a=testonly
[gecko.git] / layout / tables / nsTableRowFrame.cpp
blob37d07debf99edd7270cf8f1befab7bd618714ac8
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsTableRowFrame.h"
8 #include "mozilla/Maybe.h"
9 #include "mozilla/PresShell.h"
10 #include "nsTableRowGroupFrame.h"
11 #include "nsPresContext.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "nsStyleConsts.h"
14 #include "nsGkAtoms.h"
15 #include "nsIContent.h"
16 #include "nsIFrame.h"
17 #include "nsIFrameInlines.h"
18 #include "nsTableFrame.h"
19 #include "nsTableCellFrame.h"
20 #include "nsCSSRendering.h"
21 #include "nsHTMLParts.h"
22 #include "nsTableColGroupFrame.h"
23 #include "nsTableColFrame.h"
24 #include "nsCOMPtr.h"
25 #include "nsDisplayList.h"
26 #include "nsIFrameInlines.h"
27 #include <algorithm>
29 #ifdef ACCESSIBILITY
30 # include "nsAccessibilityService.h"
31 #endif
33 using namespace mozilla;
35 namespace mozilla {
37 struct TableCellReflowInput : public ReflowInput {
38 TableCellReflowInput(nsPresContext* aPresContext,
39 const ReflowInput& aParentReflowInput, nsIFrame* aFrame,
40 const LogicalSize& aAvailableSpace,
41 ReflowInput::InitFlags aFlags = {})
42 : ReflowInput(aPresContext, aParentReflowInput, aFrame, aAvailableSpace,
43 Nothing(), aFlags) {}
45 void FixUp(const LogicalSize& aAvailSpace);
48 } // namespace mozilla
50 void TableCellReflowInput::FixUp(const LogicalSize& aAvailSpace) {
51 // fix the mComputed values during a pass 2 reflow since the cell can be a
52 // percentage base
53 NS_WARNING_ASSERTION(
54 NS_UNCONSTRAINEDSIZE != aAvailSpace.ISize(mWritingMode),
55 "have unconstrained inline-size; this should only result from very large "
56 "sizes, not attempts at intrinsic inline size calculation");
57 if (NS_UNCONSTRAINEDSIZE != ComputedISize()) {
58 nscoord computedISize =
59 aAvailSpace.ISize(mWritingMode) -
60 ComputedLogicalBorderPadding(mWritingMode).IStartEnd(mWritingMode);
61 computedISize = std::max(0, computedISize);
62 SetComputedISize(computedISize);
64 if (NS_UNCONSTRAINEDSIZE != ComputedBSize() &&
65 NS_UNCONSTRAINEDSIZE != aAvailSpace.BSize(mWritingMode)) {
66 nscoord computedBSize =
67 aAvailSpace.BSize(mWritingMode) -
68 ComputedLogicalBorderPadding(mWritingMode).BStartEnd(mWritingMode);
69 computedBSize = std::max(0, computedBSize);
70 SetComputedBSize(computedBSize);
74 void nsTableRowFrame::InitChildReflowInput(nsPresContext& aPresContext,
75 const LogicalSize& aAvailSize,
76 bool aBorderCollapse,
77 TableCellReflowInput& aReflowInput) {
78 Maybe<LogicalMargin> collapseBorder;
79 if (aBorderCollapse) {
80 // we only reflow cells, so don't need to check frame type
81 nsBCTableCellFrame* bcCellFrame = (nsBCTableCellFrame*)aReflowInput.mFrame;
82 if (bcCellFrame) {
83 collapseBorder.emplace(
84 bcCellFrame->GetBorderWidth(aReflowInput.GetWritingMode()));
87 aReflowInput.Init(&aPresContext, Nothing(), collapseBorder);
88 aReflowInput.FixUp(aAvailSize);
91 void nsTableRowFrame::SetFixedBSize(nscoord aValue) {
92 nscoord bsize = std::max(0, aValue);
93 if (HasFixedBSize()) {
94 if (bsize > mStyleFixedBSize) {
95 mStyleFixedBSize = bsize;
97 } else {
98 mStyleFixedBSize = bsize;
99 if (bsize > 0) {
100 SetHasFixedBSize(true);
105 void nsTableRowFrame::SetPctBSize(float aPctValue, bool aForce) {
106 nscoord bsize = std::max(0, NSToCoordRound(aPctValue * 100.0f));
107 if (HasPctBSize()) {
108 if ((bsize > mStylePctBSize) || aForce) {
109 mStylePctBSize = bsize;
111 } else {
112 mStylePctBSize = bsize;
113 if (bsize > 0) {
114 SetHasPctBSize(true);
119 /* ----------- nsTableRowFrame ---------- */
121 NS_QUERYFRAME_HEAD(nsTableRowFrame)
122 NS_QUERYFRAME_ENTRY(nsTableRowFrame)
123 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
125 nsTableRowFrame::nsTableRowFrame(ComputedStyle* aStyle,
126 nsPresContext* aPresContext, ClassID aID)
127 : nsContainerFrame(aStyle, aPresContext, aID),
128 mContentBSize(0),
129 mStylePctBSize(0),
130 mStyleFixedBSize(0),
131 mMaxCellAscent(0),
132 mMaxCellDescent(0),
133 mBStartBorderWidth(0),
134 mBEndBorderWidth(0),
135 mIEndContBorderWidth(0),
136 mBStartContBorderWidth(0),
137 mIStartContBorderWidth(0) {
138 mBits.mRowIndex = 0;
139 mBits.mHasFixedBSize = 0;
140 mBits.mHasPctBSize = 0;
141 mBits.mFirstInserted = 0;
142 ResetBSize(0);
145 nsTableRowFrame::~nsTableRowFrame() = default;
147 void nsTableRowFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
148 nsIFrame* aPrevInFlow) {
149 // Let the base class do its initialization
150 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
152 NS_ASSERTION(mozilla::StyleDisplay::TableRow == StyleDisplay()->mDisplay,
153 "wrong display on table row frame");
155 if (aPrevInFlow) {
156 // Set the row index
157 nsTableRowFrame* rowFrame = (nsTableRowFrame*)aPrevInFlow;
159 SetRowIndex(rowFrame->GetRowIndex());
160 } else {
161 mWritingMode = GetTableFrame()->GetWritingMode();
165 void nsTableRowFrame::DestroyFrom(nsIFrame* aDestructRoot,
166 PostDestroyData& aPostDestroyData) {
167 if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
168 nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot);
171 nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
174 /* virtual */
175 void nsTableRowFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
176 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
178 if (!aOldComputedStyle) // avoid this on init
179 return;
181 #ifdef ACCESSIBILITY
182 if (nsAccessibilityService* accService = GetAccService()) {
183 // If a table row's background color is now different from
184 // the background color of its previous row, it is possible our
185 // table now has alternating row colors. This changes whether or not
186 // the table is classified as a layout table or data table.
187 // We invalidate on every background color change to avoid
188 // walking the tree in search of the nearest row.
189 if (StyleBackground()->BackgroundColor(this) !=
190 aOldComputedStyle->StyleBackground()->BackgroundColor(
191 aOldComputedStyle)) {
192 // We send a notification here to invalidate the a11y cache on the
193 // table so the next fetch of IsProbablyLayoutTable() is accurate.
194 accService->TableLayoutGuessMaybeChanged(PresShell(), mContent);
197 #endif
199 nsTableFrame* tableFrame = GetTableFrame();
200 if (tableFrame->IsBorderCollapse() &&
201 tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) {
202 TableArea damageArea(0, GetRowIndex(), tableFrame->GetColCount(), 1);
203 tableFrame->AddBCDamageArea(damageArea);
207 void nsTableRowFrame::AppendFrames(ChildListID aListID,
208 nsFrameList& aFrameList) {
209 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
211 DrainSelfOverflowList(); // ensure the last frame is in mFrames
212 const nsFrameList::Slice& newCells =
213 mFrames.AppendFrames(nullptr, aFrameList);
215 // Add the new cell frames to the table
216 nsTableFrame* tableFrame = GetTableFrame();
217 for (nsFrameList::Enumerator e(newCells); !e.AtEnd(); e.Next()) {
218 nsIFrame* childFrame = e.get();
219 NS_ASSERTION(childFrame->IsTableCellFrame(),
220 "Not a table cell frame/pseudo frame construction failure");
221 tableFrame->AppendCell(static_cast<nsTableCellFrame&>(*childFrame),
222 GetRowIndex());
225 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
226 NS_FRAME_HAS_DIRTY_CHILDREN);
227 tableFrame->SetGeometryDirty();
230 void nsTableRowFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
231 const nsLineList::iterator* aPrevFrameLine,
232 nsFrameList& aFrameList) {
233 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
234 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
235 "inserting after sibling frame with different parent");
236 if (mFrames.IsEmpty() || (aPrevFrame && !aPrevFrame->GetNextSibling())) {
237 // This is actually an append (though our caller didn't figure that out),
238 // and our append codepath is both simpler/faster _and_ less buggy.
239 // https://bugzilla.mozilla.org/show_bug.cgi?id=1388898 tracks the bugginess
240 AppendFrames(aListID, aFrameList);
241 return;
244 DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
245 // Insert Frames in the frame list
246 const nsFrameList::Slice& newCells =
247 mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
249 nsTableCellFrame* prevCellFrame =
250 static_cast<nsTableCellFrame*>(nsTableFrame::GetFrameAtOrBefore(
251 this, aPrevFrame, LayoutFrameType::TableCell));
252 nsTArray<nsTableCellFrame*> cellChildren;
253 for (nsFrameList::Enumerator e(newCells); !e.AtEnd(); e.Next()) {
254 nsIFrame* childFrame = e.get();
255 NS_ASSERTION(childFrame->IsTableCellFrame(),
256 "Not a table cell frame/pseudo frame construction failure");
257 cellChildren.AppendElement(static_cast<nsTableCellFrame*>(childFrame));
259 // insert the cells into the cell map
260 int32_t colIndex = -1;
261 if (prevCellFrame) {
262 colIndex = prevCellFrame->ColIndex();
264 nsTableFrame* tableFrame = GetTableFrame();
265 tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex);
267 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
268 NS_FRAME_HAS_DIRTY_CHILDREN);
269 tableFrame->SetGeometryDirty();
272 void nsTableRowFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
273 NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
275 MOZ_ASSERT((nsTableCellFrame*)do_QueryFrame(aOldFrame));
276 nsTableCellFrame* cellFrame = static_cast<nsTableCellFrame*>(aOldFrame);
277 // remove the cell from the cell map
278 nsTableFrame* tableFrame = GetTableFrame();
279 tableFrame->RemoveCell(cellFrame, GetRowIndex());
281 // Remove the frame and destroy it
282 mFrames.DestroyFrame(aOldFrame);
284 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
285 NS_FRAME_HAS_DIRTY_CHILDREN);
287 tableFrame->SetGeometryDirty();
290 /* virtual */
291 nsMargin nsTableRowFrame::GetUsedMargin() const { return nsMargin(0, 0, 0, 0); }
293 /* virtual */
294 nsMargin nsTableRowFrame::GetUsedBorder() const { return nsMargin(0, 0, 0, 0); }
296 /* virtual */
297 nsMargin nsTableRowFrame::GetUsedPadding() const {
298 return nsMargin(0, 0, 0, 0);
301 static nscoord GetBSizeOfRowsSpannedBelowFirst(
302 nsTableCellFrame& aTableCellFrame, nsTableFrame& aTableFrame,
303 const WritingMode aWM) {
304 nscoord bsize = 0;
305 int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(aTableCellFrame);
306 // add in bsize of rows spanned beyond the 1st one
307 nsIFrame* nextRow = aTableCellFrame.GetParent()->GetNextSibling();
308 for (int32_t rowX = 1; ((rowX < rowSpan) && nextRow);) {
309 if (nextRow->IsTableRowFrame()) {
310 bsize += nextRow->BSize(aWM);
311 rowX++;
313 bsize += aTableFrame.GetRowSpacing(rowX);
314 nextRow = nextRow->GetNextSibling();
316 return bsize;
320 * Post-reflow hook. This is where the table row does its post-processing
322 void nsTableRowFrame::DidResize() {
323 // Resize and re-align the cell frames based on our row bsize
324 nsTableFrame* tableFrame = GetTableFrame();
326 WritingMode wm = GetWritingMode();
327 ReflowOutput desiredSize(wm);
328 desiredSize.SetSize(wm, GetLogicalSize(wm));
329 desiredSize.SetOverflowAreasToDesiredBounds();
331 nsSize containerSize = mRect.Size();
333 for (nsIFrame* childFrame : mFrames) {
334 nsTableCellFrame* cellFrame = do_QueryFrame(childFrame);
335 if (cellFrame) {
336 nscoord cellBSize = BSize(wm) + GetBSizeOfRowsSpannedBelowFirst(
337 *cellFrame, *tableFrame, wm);
339 // If the bsize for the cell has changed, we need to reset it;
340 // and in vertical-rl mode, we need to update the cell's block position
341 // to account for the containerSize, which may not have been known
342 // earlier, so we always apply it here.
343 LogicalSize cellSize = cellFrame->GetLogicalSize(wm);
344 if (cellSize.BSize(wm) != cellBSize || wm.IsVerticalRL()) {
345 nsRect cellOldRect = cellFrame->GetRect();
346 nsRect cellInkOverflow = cellFrame->InkOverflowRect();
348 if (wm.IsVerticalRL()) {
349 // Get the old position of the cell, as we want to preserve its
350 // inline coordinate.
351 LogicalPoint oldPos =
352 cellFrame->GetLogicalPosition(wm, containerSize);
354 // The cell should normally be aligned with the row's block-start,
355 // so set the B component of the position to zero:
356 LogicalPoint newPos(wm, oldPos.I(wm), 0);
358 // ...unless relative positioning is in effect, in which case the
359 // cell may have been moved away from the row's block-start
360 if (cellFrame->IsRelativelyOrStickyPositioned()) {
361 // Find out where the cell would have been without relative
362 // positioning.
363 LogicalPoint oldNormalPos =
364 cellFrame->GetLogicalNormalPosition(wm, containerSize);
365 // The difference (if any) between oldPos and oldNormalPos reflects
366 // relative positioning that was applied to the cell, and which we
367 // need to incorporate when resetting the position.
368 newPos.B(wm) = oldPos.B(wm) - oldNormalPos.B(wm);
371 if (oldPos != newPos) {
372 cellFrame->SetPosition(wm, newPos, containerSize);
373 nsTableFrame::RePositionViews(cellFrame);
377 cellSize.BSize(wm) = cellBSize;
378 cellFrame->SetSize(wm, cellSize);
380 nsTableFrame* tableFrame = GetTableFrame();
381 if (tableFrame->IsBorderCollapse()) {
382 nsTableFrame::InvalidateTableFrame(cellFrame, cellOldRect,
383 cellInkOverflow, false);
387 // realign cell content based on the new bsize. We might be able to
388 // skip this if the bsize didn't change... maybe. Hard to tell.
389 cellFrame->BlockDirAlignChild(wm, mMaxCellAscent);
391 // Always store the overflow, even if the height didn't change, since
392 // we'll lose part of our overflow area otherwise.
393 ConsiderChildOverflow(desiredSize.mOverflowAreas, cellFrame);
395 // Note that if the cell's *content* needs to change in response
396 // to this height, it will get a special bsize reflow.
399 FinishAndStoreOverflow(&desiredSize);
400 if (HasView()) {
401 nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, GetView(),
402 desiredSize.InkOverflow(),
403 ReflowChildFlags::Default);
405 // Let our base class do the usual work
408 // returns max-ascent amongst all cells that have 'vertical-align: baseline'
409 // *including* cells with rowspans
410 nscoord nsTableRowFrame::GetMaxCellAscent() const { return mMaxCellAscent; }
412 nscoord nsTableRowFrame::GetRowBaseline(WritingMode aWM) {
413 if (mMaxCellAscent) {
414 return mMaxCellAscent;
417 // If we get here, we don't have a baseline on any of the cells in this row.
419 nscoord ascent = 0;
420 for (nsIFrame* childFrame : mFrames) {
421 MOZ_ASSERT(childFrame->IsTableCellFrame());
422 nscoord s = childFrame->SynthesizeBaselineBOffsetFromContentBox(
423 aWM, BaselineSharingGroup::First);
424 ascent = std::max(ascent, s);
426 return ascent;
429 nscoord nsTableRowFrame::GetInitialBSize(nscoord aPctBasis) const {
430 nscoord bsize = 0;
431 if ((aPctBasis > 0) && HasPctBSize()) {
432 bsize = NSToCoordRound(GetPctBSize() * (float)aPctBasis);
434 if (HasFixedBSize()) {
435 bsize = std::max(bsize, GetFixedBSize());
437 return std::max(bsize, GetContentBSize());
440 void nsTableRowFrame::ResetBSize(nscoord aFixedBSize) {
441 SetHasFixedBSize(false);
442 SetHasPctBSize(false);
443 SetFixedBSize(0);
444 SetPctBSize(0);
445 SetContentBSize(0);
447 if (aFixedBSize > 0) {
448 SetFixedBSize(aFixedBSize);
451 mMaxCellAscent = 0;
452 mMaxCellDescent = 0;
455 void nsTableRowFrame::UpdateBSize(nscoord aBSize, nscoord aAscent,
456 nscoord aDescent, nsTableFrame* aTableFrame,
457 nsTableCellFrame* aCellFrame) {
458 if (!aTableFrame || !aCellFrame) {
459 NS_ASSERTION(false, "invalid call");
460 return;
463 if (aBSize != NS_UNCONSTRAINEDSIZE) {
464 if (!(aCellFrame->HasVerticalAlignBaseline())) { // only the cell's height
465 // matters
466 if (GetInitialBSize() < aBSize) {
467 int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame);
468 if (rowSpan == 1) {
469 SetContentBSize(aBSize);
472 } else { // the alignment on the baseline can change the bsize
473 NS_ASSERTION((aAscent != NS_UNCONSTRAINEDSIZE) &&
474 (aDescent != NS_UNCONSTRAINEDSIZE),
475 "invalid call");
476 // see if this is a long ascender
477 if (mMaxCellAscent < aAscent) {
478 mMaxCellAscent = aAscent;
480 // see if this is a long descender and without rowspan
481 if (mMaxCellDescent < aDescent) {
482 int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame);
483 if (rowSpan == 1) {
484 mMaxCellDescent = aDescent;
487 // keep the tallest bsize in sync
488 if (GetInitialBSize() < mMaxCellAscent + mMaxCellDescent) {
489 SetContentBSize(mMaxCellAscent + mMaxCellDescent);
495 nscoord nsTableRowFrame::CalcBSize(const ReflowInput& aReflowInput) {
496 nsTableFrame* tableFrame = GetTableFrame();
497 nscoord computedBSize = (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedBSize())
499 : aReflowInput.ComputedBSize();
500 ResetBSize(computedBSize);
502 WritingMode wm = aReflowInput.GetWritingMode();
503 const nsStylePosition* position = StylePosition();
504 const auto& bsizeStyleCoord = position->BSize(wm);
505 if (bsizeStyleCoord.ConvertsToLength()) {
506 SetFixedBSize(bsizeStyleCoord.ToLength());
507 } else if (bsizeStyleCoord.ConvertsToPercentage()) {
508 SetPctBSize(bsizeStyleCoord.ToPercentage());
511 for (nsIFrame* kidFrame : mFrames) {
512 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
513 if (cellFrame) {
514 MOZ_ASSERT(cellFrame->GetWritingMode() == wm);
515 LogicalSize desSize = cellFrame->GetDesiredSize();
516 if ((NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) &&
517 !GetPrevInFlow()) {
518 CalculateCellActualBSize(cellFrame, desSize.BSize(wm), wm);
520 // bsize may have changed, adjust descent to absorb any excess difference
521 nscoord ascent;
522 if (!kidFrame->PrincipalChildList()
523 .FirstChild()
524 ->PrincipalChildList()
525 .FirstChild())
526 ascent = desSize.BSize(wm);
527 else
528 ascent = cellFrame->GetCellBaseline();
529 nscoord descent = desSize.BSize(wm) - ascent;
530 UpdateBSize(desSize.BSize(wm), ascent, descent, tableFrame, cellFrame);
533 return GetInitialBSize();
536 void nsTableRowFrame::PaintCellBackgroundsForFrame(
537 nsIFrame* aFrame, nsDisplayListBuilder* aBuilder,
538 const nsDisplayListSet& aLists, const nsPoint& aOffset) {
539 // Compute background rect by iterating all cell frame.
540 const nsPoint toReferenceFrame = aBuilder->ToReferenceFrame(aFrame);
541 for (nsTableCellFrame* cell = GetFirstCell(); cell;
542 cell = cell->GetNextCell()) {
543 if (!cell->ShouldPaintBackground(aBuilder)) {
544 continue;
547 auto cellRect =
548 cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset;
549 if (!aBuilder->GetDirtyRect().Intersects(cellRect)) {
550 continue;
552 cellRect += toReferenceFrame;
553 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
554 aBuilder, aFrame, cellRect, aLists.BorderBackground(), true, nullptr,
555 aFrame->GetRectRelativeToSelf() + toReferenceFrame, cell);
559 void nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
560 const nsDisplayListSet& aLists) {
561 DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground());
563 PaintCellBackgroundsForFrame(this, aBuilder, aLists);
565 DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground());
567 DisplayOutline(aBuilder, aLists);
569 for (nsIFrame* kid : PrincipalChildList()) {
570 BuildDisplayListForChild(aBuilder, kid, aLists);
574 LogicalSides nsTableRowFrame::GetLogicalSkipSides() const {
575 LogicalSides skip(mWritingMode);
576 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
577 StyleBoxDecorationBreak::Clone)) {
578 return skip;
581 if (GetPrevInFlow()) {
582 skip |= eLogicalSideBitsBStart;
584 if (GetNextInFlow()) {
585 skip |= eLogicalSideBitsBEnd;
587 return skip;
590 // Calculate the cell's actual bsize given its pass2 bsize.
591 // Takes into account the specified bsize (in the style).
592 // Modifies the desired bsize that is passed in.
593 nsresult nsTableRowFrame::CalculateCellActualBSize(nsTableCellFrame* aCellFrame,
594 nscoord& aDesiredBSize,
595 WritingMode aWM) {
596 nscoord specifiedBSize = 0;
598 // Get the bsize specified in the style information
599 const nsStylePosition* position = aCellFrame->StylePosition();
601 int32_t rowSpan = GetTableFrame()->GetEffectiveRowSpan(*aCellFrame);
603 const auto& bsizeStyleCoord = position->BSize(aWM);
604 if (bsizeStyleCoord.ConvertsToLength()) {
605 // In quirks mode, table cell isize should be content-box, but bsize
606 // should be border-box.
607 // Because of this historic anomaly, we do not use quirk.css
608 // (since we can't specify one value of box-sizing for isize and another
609 // for bsize)
610 specifiedBSize = bsizeStyleCoord.ToLength();
611 if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks &&
612 position->mBoxSizing == StyleBoxSizing::Content) {
613 specifiedBSize +=
614 aCellFrame->GetLogicalUsedBorderAndPadding(aWM).BStartEnd(aWM);
617 if (1 == rowSpan) {
618 SetFixedBSize(specifiedBSize);
620 } else if (bsizeStyleCoord.ConvertsToPercentage()) {
621 if (1 == rowSpan) {
622 SetPctBSize(bsizeStyleCoord.ToPercentage());
626 // If the specified bsize is greater than the desired bsize,
627 // then use the specified bsize
628 if (specifiedBSize > aDesiredBSize) {
629 aDesiredBSize = specifiedBSize;
632 return NS_OK;
635 // Calculates the available isize for the table cell based on the known
636 // column isizes taking into account column spans and column spacing
637 static nscoord CalcAvailISize(nsTableFrame& aTableFrame,
638 nsTableCellFrame& aCellFrame) {
639 nscoord cellAvailISize = 0;
640 uint32_t colIndex = aCellFrame.ColIndex();
641 int32_t colspan = aTableFrame.GetEffectiveColSpan(aCellFrame);
642 NS_ASSERTION(colspan > 0, "effective colspan should be positive");
643 nsTableFrame* fifTable =
644 static_cast<nsTableFrame*>(aTableFrame.FirstInFlow());
646 for (int32_t spanX = 0; spanX < colspan; spanX++) {
647 cellAvailISize += fifTable->GetColumnISizeFromFirstInFlow(colIndex + spanX);
648 if (spanX > 0 && aTableFrame.ColumnHasCellSpacingBefore(colIndex + spanX)) {
649 cellAvailISize += aTableFrame.GetColSpacing(colIndex + spanX - 1);
652 return cellAvailISize;
655 static nscoord GetSpaceBetween(int32_t aPrevColIndex, int32_t aColIndex,
656 int32_t aColSpan, nsTableFrame& aTableFrame,
657 bool aCheckVisibility) {
658 nscoord space = 0;
659 int32_t colIdx;
660 nsTableFrame* fifTable =
661 static_cast<nsTableFrame*>(aTableFrame.FirstInFlow());
662 for (colIdx = aPrevColIndex + 1; aColIndex > colIdx; colIdx++) {
663 bool isCollapsed = false;
664 if (!aCheckVisibility) {
665 space += fifTable->GetColumnISizeFromFirstInFlow(colIdx);
666 } else {
667 nsTableColFrame* colFrame = aTableFrame.GetColFrame(colIdx);
668 const nsStyleVisibility* colVis = colFrame->StyleVisibility();
669 bool collapseCol = StyleVisibility::Collapse == colVis->mVisible;
670 nsIFrame* cgFrame = colFrame->GetParent();
671 const nsStyleVisibility* groupVis = cgFrame->StyleVisibility();
672 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
673 isCollapsed = collapseCol || collapseGroup;
674 if (!isCollapsed)
675 space += fifTable->GetColumnISizeFromFirstInFlow(colIdx);
677 if (!isCollapsed && aTableFrame.ColumnHasCellSpacingBefore(colIdx)) {
678 space += aTableFrame.GetColSpacing(colIdx - 1);
681 return space;
684 // subtract the bsizes of aRow's prev in flows from the unpaginated bsize
685 static nscoord CalcBSizeFromUnpaginatedBSize(nsTableRowFrame& aRow,
686 WritingMode aWM) {
687 nscoord bsize = 0;
688 nsTableRowFrame* firstInFlow =
689 static_cast<nsTableRowFrame*>(aRow.FirstInFlow());
690 if (firstInFlow->HasUnpaginatedBSize()) {
691 bsize = firstInFlow->GetUnpaginatedBSize();
692 for (nsIFrame* prevInFlow = aRow.GetPrevInFlow(); prevInFlow;
693 prevInFlow = prevInFlow->GetPrevInFlow()) {
694 bsize -= prevInFlow->BSize(aWM);
697 return std::max(bsize, 0);
700 void nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext,
701 ReflowOutput& aDesiredSize,
702 const ReflowInput& aReflowInput,
703 nsTableFrame& aTableFrame,
704 nsReflowStatus& aStatus) {
705 aStatus.Reset();
707 // XXXldb Should we be checking constrained bsize instead?
708 const bool isPaginated = aPresContext->IsPaginated();
709 const bool borderCollapse = aTableFrame.IsBorderCollapse();
711 int32_t cellColSpan =
712 1; // must be defined here so it's set properly for non-cell kids
714 // remember the col index of the previous cell to handle rowspans into this
715 // row
716 int32_t prevColIndex = -1;
717 nscoord iCoord = 0; // running total of children inline-coord offset
719 // This computes the max of all cell bsizes
720 nscoord cellMaxBSize = 0;
722 // Reflow each of our existing cell frames
723 WritingMode wm = aReflowInput.GetWritingMode();
724 nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
726 for (nsIFrame* kidFrame : mFrames) {
727 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
728 if (!cellFrame) {
729 // XXXldb nsCSSFrameConstructor needs to enforce this!
730 MOZ_ASSERT_UNREACHABLE("yikes, a non-row child");
732 // it's an unknown frame type, give it a generic reflow and ignore the
733 // results
734 TableCellReflowInput kidReflowInput(
735 aPresContext, aReflowInput, kidFrame,
736 LogicalSize(kidFrame->GetWritingMode(), 0, 0),
737 ReflowInput::InitFlag::CallerWillInit);
738 InitChildReflowInput(*aPresContext, LogicalSize(wm), false,
739 kidReflowInput);
740 ReflowOutput desiredSize(aReflowInput);
741 nsReflowStatus status;
742 ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowInput, 0, 0,
743 ReflowChildFlags::Default, status);
744 kidFrame->DidReflow(aPresContext, nullptr);
746 continue;
749 // See if we should only reflow the dirty child frames
750 bool doReflowChild = true;
751 if (!aReflowInput.ShouldReflowAllKids() && !aTableFrame.IsGeometryDirty() &&
752 !kidFrame->IsSubtreeDirty()) {
753 if (!aReflowInput.mFlags.mSpecialBSizeReflow) doReflowChild = false;
754 } else if ((NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize())) {
755 // We don't reflow a rowspan >1 cell here with a constrained bsize.
756 // That happens in nsTableRowGroupFrame::SplitSpanningCells.
757 if (aTableFrame.GetEffectiveRowSpan(*cellFrame) > 1) {
758 doReflowChild = false;
761 if (aReflowInput.mFlags.mSpecialBSizeReflow) {
762 if (!isPaginated &&
763 !cellFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
764 continue;
768 uint32_t cellColIndex = cellFrame->ColIndex();
769 cellColSpan = aTableFrame.GetEffectiveColSpan(*cellFrame);
771 // If the adjacent cell is in a prior row (because of a rowspan) add in the
772 // space NOTE: prevColIndex can be -1 here.
773 if (prevColIndex != (static_cast<int32_t>(cellColIndex) - 1)) {
774 iCoord += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan,
775 aTableFrame, false);
778 // remember the rightmost (ltr) or leftmost (rtl) column this cell spans
779 // into
780 prevColIndex = cellColIndex + (cellColSpan - 1);
782 // Reflow the child frame
783 nsRect kidRect = kidFrame->GetRect();
784 LogicalPoint origKidNormalPosition =
785 kidFrame->GetLogicalNormalPosition(wm, containerSize);
786 // All cells' no-relative-positioning position should be snapped to the
787 // row's bstart edge.
788 // This doesn't hold in vertical-rl mode, where we don't yet know the
789 // correct containerSize for the row frame. In that case, we'll have to
790 // fix up child positions later, after determining our desiredSize.
791 NS_ASSERTION(origKidNormalPosition.B(wm) == 0 || wm.IsVerticalRL(),
792 "unexpected kid position");
794 nsRect kidInkOverflow = kidFrame->InkOverflowRect();
795 LogicalPoint kidPosition(wm, iCoord, 0);
796 bool firstReflow = kidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
798 if (doReflowChild) {
799 // Calculate the available isize for the table cell using the known
800 // column isizes
801 nscoord availCellISize = CalcAvailISize(aTableFrame, *cellFrame);
803 Maybe<TableCellReflowInput> kidReflowInput;
804 ReflowOutput desiredSize(aReflowInput);
806 // If the avail isize is not the same as last time we reflowed the cell or
807 // the cell wants to be bigger than what was available last time or
808 // it is a style change reflow or we are printing, then we must reflow the
809 // cell. Otherwise we can skip the reflow.
810 // XXXldb Why is this condition distinct from doReflowChild above?
811 WritingMode wm = aReflowInput.GetWritingMode();
812 NS_ASSERTION(cellFrame->GetWritingMode() == wm,
813 "expected consistent writing-mode within table");
814 LogicalSize cellDesiredSize = cellFrame->GetDesiredSize();
815 if ((availCellISize != cellFrame->GetPriorAvailISize()) ||
816 (cellDesiredSize.ISize(wm) > cellFrame->GetPriorAvailISize()) ||
817 HasAnyStateBits(NS_FRAME_IS_DIRTY) || isPaginated ||
818 cellFrame->IsSubtreeDirty() ||
819 // See if it needs a special reflow, or if it had one that we need to
820 // undo.
821 cellFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE) ||
822 HasPctBSize()) {
823 // Reflow the cell to fit the available isize, bsize
824 // XXX The old IR_ChildIsDirty code used availCellISize here.
825 LogicalSize kidAvailSize(wm, availCellISize,
826 aReflowInput.AvailableBSize());
828 // Reflow the child
829 kidReflowInput.emplace(aPresContext, aReflowInput, kidFrame,
830 kidAvailSize,
831 ReflowInput::InitFlag::CallerWillInit);
832 InitChildReflowInput(*aPresContext, kidAvailSize, borderCollapse,
833 *kidReflowInput);
835 nsReflowStatus status;
836 ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowInput, wm,
837 kidPosition, containerSize, ReflowChildFlags::Default,
838 status);
840 // allow the table to determine if/how the table needs to be rebalanced
841 // If any of the cells are not complete, then we're not complete
842 if (status.IsIncomplete()) {
843 aStatus.Reset();
844 aStatus.SetIncomplete();
846 } else {
847 if (iCoord != origKidNormalPosition.I(wm)) {
848 kidFrame->InvalidateFrameSubtree();
851 desiredSize.SetSize(wm, cellDesiredSize);
852 desiredSize.mOverflowAreas = cellFrame->GetOverflowAreas();
854 // if we are in a floated table, our position is not yet established, so
855 // we cannot reposition our views the containing block will do this for
856 // us after positioning the table
857 if (!aTableFrame.IsFloating()) {
858 // Because we may have moved the frame we need to make sure any views
859 // are positioned properly. We have to do this, because any one of our
860 // parent frames could have moved and we have no way of knowing...
861 nsTableFrame::RePositionViews(kidFrame);
865 if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) {
866 if (!GetPrevInFlow()) {
867 // Calculate the cell's actual bsize given its pass2 bsize. This
868 // function takes into account the specified bsize (in the style)
869 CalculateCellActualBSize(cellFrame, desiredSize.BSize(wm), wm);
871 // bsize may have changed, adjust descent to absorb any excess
872 // difference
873 nscoord ascent;
874 if (!kidFrame->PrincipalChildList()
875 .FirstChild()
876 ->PrincipalChildList()
877 .FirstChild()) {
878 ascent = desiredSize.BSize(wm);
879 } else {
880 ascent = ((nsTableCellFrame*)kidFrame)->GetCellBaseline();
882 nscoord descent = desiredSize.BSize(wm) - ascent;
883 UpdateBSize(desiredSize.BSize(wm), ascent, descent, &aTableFrame,
884 cellFrame);
885 } else {
886 cellMaxBSize = std::max(cellMaxBSize, desiredSize.BSize(wm));
887 int32_t rowSpan =
888 aTableFrame.GetEffectiveRowSpan((nsTableCellFrame&)*kidFrame);
889 if (1 == rowSpan) {
890 SetContentBSize(cellMaxBSize);
894 // Place the child
895 desiredSize.ISize(wm) = availCellISize;
897 ReflowChildFlags flags = ReflowChildFlags::Default;
899 if (kidReflowInput) {
900 // We reflowed. Apply relative positioning in the normal way.
901 flags = ReflowChildFlags::ApplyRelativePositioning;
902 } else if (kidFrame->IsRelativelyOrStickyPositioned()) {
903 // We didn't reflow. Do the positioning part of what
904 // MovePositionBy does internally. (This codepath should really
905 // be merged into the else below if we can.)
906 nsMargin* computedOffsetProp =
907 kidFrame->GetProperty(nsIFrame::ComputedOffsetProperty());
909 // On our fist reflow sticky children may not have the property yet (we
910 // need to reflow the children first to size the scroll frame).
911 LogicalMargin computedOffsets(
912 wm, computedOffsetProp ? *computedOffsetProp : nsMargin());
913 ReflowInput::ApplyRelativePositioning(kidFrame, wm, computedOffsets,
914 &kidPosition, containerSize);
917 // In vertical-rl mode, we are likely to have containerSize.width = 0
918 // because ComputedWidth() was NS_UNCONSTRAINEDSIZE.
919 // For cases where that's wrong, we will fix up the position later.
920 FinishReflowChild(kidFrame, aPresContext, desiredSize,
921 kidReflowInput.ptrOr(nullptr), wm, kidPosition,
922 containerSize, flags);
924 nsTableFrame* tableFrame = GetTableFrame();
925 if (tableFrame->IsBorderCollapse()) {
926 nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidInkOverflow,
927 firstReflow);
930 iCoord += desiredSize.ISize(wm);
931 } else {
932 if (iCoord != origKidNormalPosition.I(wm)) {
933 // Invalidate the old position
934 kidFrame->InvalidateFrameSubtree();
935 // Move to the new position. As above, we need to account for relative
936 // positioning.
937 kidFrame->MovePositionBy(
938 wm, LogicalPoint(wm, iCoord - origKidNormalPosition.I(wm), 0));
939 nsTableFrame::RePositionViews(kidFrame);
940 // invalidate the new position
941 kidFrame->InvalidateFrameSubtree();
943 // we need to account for the cell's isize even if it isn't reflowed
944 iCoord += kidFrame->ISize(wm);
946 if (kidFrame->GetNextInFlow()) {
947 aStatus.Reset();
948 aStatus.SetIncomplete();
951 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
952 iCoord += aTableFrame.GetColSpacing(cellColIndex);
955 // Just set our isize to what was available.
956 // The table will calculate the isize and not use our value.
957 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
959 if (aReflowInput.mFlags.mSpecialBSizeReflow) {
960 aDesiredSize.BSize(wm) = BSize(wm);
961 } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) {
962 aDesiredSize.BSize(wm) = CalcBSize(aReflowInput);
963 if (GetPrevInFlow()) {
964 nscoord bsize = CalcBSizeFromUnpaginatedBSize(*this, wm);
965 aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bsize);
966 } else {
967 if (isPaginated && HasStyleBSize()) {
968 // set the unpaginated bsize so next in flows can try to honor it
969 SetHasUnpaginatedBSize(true);
970 SetUnpaginatedBSize(aPresContext, aDesiredSize.BSize(wm));
972 if (isPaginated && HasUnpaginatedBSize()) {
973 aDesiredSize.BSize(wm) =
974 std::max(aDesiredSize.BSize(wm), GetUnpaginatedBSize());
977 } else { // constrained bsize, paginated
978 // Compute the bsize we should have from style (subtracting the
979 // bsize from our prev-in-flows from the style bsize)
980 nscoord styleBSize = CalcBSizeFromUnpaginatedBSize(*this, wm);
981 if (styleBSize > aReflowInput.AvailableBSize()) {
982 styleBSize = aReflowInput.AvailableBSize();
983 aStatus.SetIncomplete();
985 aDesiredSize.BSize(wm) = std::max(cellMaxBSize, styleBSize);
988 if (wm.IsVerticalRL()) {
989 // Any children whose width was not the same as our final
990 // aDesiredSize.BSize will have been misplaced earlier at the
991 // FinishReflowChild stage. So fix them up now.
992 for (nsIFrame* kidFrame : mFrames) {
993 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
994 if (!cellFrame) {
995 continue;
997 if (kidFrame->BSize(wm) != aDesiredSize.BSize(wm)) {
998 kidFrame->MovePositionBy(
1000 LogicalPoint(wm, 0, kidFrame->BSize(wm) - aDesiredSize.BSize(wm)));
1001 nsTableFrame::RePositionViews(kidFrame);
1002 // Do we need to InvalidateFrameSubtree() here?
1007 aDesiredSize.UnionOverflowAreasWithDesiredBounds();
1008 FinishAndStoreOverflow(&aDesiredSize);
1011 /** Layout the entire row.
1012 * This method stacks cells in the inline dir according to HTML 4.0 rules.
1014 void nsTableRowFrame::Reflow(nsPresContext* aPresContext,
1015 ReflowOutput& aDesiredSize,
1016 const ReflowInput& aReflowInput,
1017 nsReflowStatus& aStatus) {
1018 MarkInReflow();
1019 DO_GLOBAL_REFLOW_COUNT("nsTableRowFrame");
1020 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
1021 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1023 WritingMode wm = aReflowInput.GetWritingMode();
1025 nsTableFrame* tableFrame = GetTableFrame();
1026 const nsStyleVisibility* rowVis = StyleVisibility();
1027 bool collapseRow = StyleVisibility::Collapse == rowVis->mVisible;
1028 if (collapseRow) {
1029 tableFrame->SetNeedToCollapse(true);
1032 // see if a special bsize reflow needs to occur due to having a pct bsize
1033 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
1035 // See if we have a cell with specified/pct bsize
1036 InitHasCellWithStyleBSize(tableFrame);
1038 ReflowChildren(aPresContext, aDesiredSize, aReflowInput, *tableFrame,
1039 aStatus);
1041 if (aPresContext->IsPaginated() && !aStatus.IsFullyComplete() &&
1042 ShouldAvoidBreakInside(aReflowInput)) {
1043 aStatus.SetInlineLineBreakBeforeAndReset();
1046 // Just set our isize to what was available.
1047 // The table will calculate the isize and not use our value.
1048 aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
1050 // If our parent is in initial reflow, it'll handle invalidating our
1051 // entire overflow rect.
1052 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
1053 nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
1054 InvalidateFrame();
1057 // Any absolutely-positioned children will get reflowed in
1058 // nsIFrame::FixupPositionedTableParts in another pass, so propagate our
1059 // dirtiness to them before our parent clears our dirty bits.
1060 PushDirtyBitToAbsoluteFrames();
1062 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
1066 * This function is called by the row group frame's SplitRowGroup() code when
1067 * pushing a row frame that has cell frames that span into it. The cell frame
1068 * should be reflowed with the specified height
1070 nscoord nsTableRowFrame::ReflowCellFrame(nsPresContext* aPresContext,
1071 const ReflowInput& aReflowInput,
1072 bool aIsTopOfPage,
1073 nsTableCellFrame* aCellFrame,
1074 nscoord aAvailableBSize,
1075 nsReflowStatus& aStatus) {
1076 WritingMode wm = aReflowInput.GetWritingMode();
1078 // Reflow the cell frame with the specified height. Use the existing width
1079 nsSize containerSize = aCellFrame->GetSize();
1080 LogicalRect cellRect = aCellFrame->GetLogicalRect(wm, containerSize);
1081 nsRect cellInkOverflow = aCellFrame->InkOverflowRect();
1083 LogicalSize cellSize = cellRect.Size(wm);
1084 LogicalSize availSize(wm, cellRect.ISize(wm), aAvailableBSize);
1085 bool borderCollapse = GetTableFrame()->IsBorderCollapse();
1086 NS_ASSERTION(aCellFrame->GetWritingMode() == wm,
1087 "expected consistent writing-mode within table");
1088 TableCellReflowInput cellReflowInput(aPresContext, aReflowInput, aCellFrame,
1089 availSize,
1090 ReflowInput::InitFlag::CallerWillInit);
1091 InitChildReflowInput(*aPresContext, availSize, borderCollapse,
1092 cellReflowInput);
1093 cellReflowInput.mFlags.mIsTopOfPage = aIsTopOfPage;
1095 ReflowOutput desiredSize(aReflowInput);
1097 ReflowChild(aCellFrame, aPresContext, desiredSize, cellReflowInput, 0, 0,
1098 ReflowChildFlags::NoMoveFrame, aStatus);
1099 bool fullyComplete = aStatus.IsComplete() && !aStatus.IsTruncated();
1100 if (fullyComplete) {
1101 desiredSize.BSize(wm) = aAvailableBSize;
1103 aCellFrame->SetSize(
1104 wm, LogicalSize(wm, cellSize.ISize(wm), desiredSize.BSize(wm)));
1106 // Note: BlockDirAlignChild can affect the overflow rect.
1107 // XXX What happens if this cell has 'vertical-align: baseline' ?
1108 // XXX Why is it assumed that the cell's ascent hasn't changed ?
1109 if (fullyComplete) {
1110 aCellFrame->BlockDirAlignChild(wm, mMaxCellAscent);
1113 nsTableFrame::InvalidateTableFrame(
1114 aCellFrame, cellRect.GetPhysicalRect(wm, containerSize), cellInkOverflow,
1115 aCellFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW));
1117 aCellFrame->DidReflow(aPresContext, nullptr);
1119 return desiredSize.BSize(wm);
1122 nscoord nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset,
1123 nscoord aISize,
1124 bool aCollapseGroup,
1125 bool& aDidCollapse) {
1126 const nsStyleVisibility* rowVis = StyleVisibility();
1127 bool collapseRow = StyleVisibility::Collapse == rowVis->mVisible;
1128 nsTableFrame* tableFrame =
1129 static_cast<nsTableFrame*>(GetTableFrame()->FirstInFlow());
1130 if (collapseRow) {
1131 tableFrame->SetNeedToCollapse(true);
1134 if (aRowOffset != 0) {
1135 // We're moving, so invalidate our old position
1136 InvalidateFrameSubtree();
1139 WritingMode wm = GetWritingMode();
1141 nsSize parentSize = GetParent()->GetSize();
1142 LogicalRect rowRect = GetLogicalRect(wm, parentSize);
1143 nsRect oldRect = mRect;
1144 nsRect oldInkOverflow = InkOverflowRect();
1146 rowRect.BStart(wm) -= aRowOffset;
1147 rowRect.ISize(wm) = aISize;
1148 OverflowAreas overflow;
1149 nscoord shift = 0;
1150 nsSize containerSize = mRect.Size();
1152 if (aCollapseGroup || collapseRow) {
1153 aDidCollapse = true;
1154 shift = rowRect.BSize(wm);
1155 nsTableCellFrame* cellFrame = GetFirstCell();
1156 if (cellFrame) {
1157 uint32_t rowIndex = cellFrame->RowIndex();
1158 shift += tableFrame->GetRowSpacing(rowIndex);
1159 while (cellFrame) {
1160 LogicalRect cRect = cellFrame->GetLogicalRect(wm, containerSize);
1161 // If aRowOffset != 0, there's no point in invalidating the cells, since
1162 // we've already invalidated our overflow area. Note that we _do_ still
1163 // need to invalidate if our row is not moving, because the cell might
1164 // span out of this row, so invalidating our row rect won't do enough.
1165 if (aRowOffset == 0) {
1166 InvalidateFrame();
1168 cRect.BSize(wm) = 0;
1169 cellFrame->SetRect(wm, cRect, containerSize);
1170 cellFrame = cellFrame->GetNextCell();
1172 } else {
1173 shift += tableFrame->GetRowSpacing(GetRowIndex());
1175 rowRect.BSize(wm) = 0;
1176 } else { // row is not collapsed
1177 // remember the col index of the previous cell to handle rowspans into this
1178 // row
1179 int32_t prevColIndex = -1;
1180 nscoord iPos = 0; // running total of children inline-axis offset
1181 nsTableFrame* fifTable =
1182 static_cast<nsTableFrame*>(tableFrame->FirstInFlow());
1184 for (nsIFrame* kidFrame : mFrames) {
1185 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
1186 if (cellFrame) {
1187 uint32_t cellColIndex = cellFrame->ColIndex();
1188 int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame);
1190 // If the adjacent cell is in a prior row (because of a rowspan) add in
1191 // the space
1192 // NOTE: prevColIndex can be -1 here.
1193 if (prevColIndex != (static_cast<int32_t>(cellColIndex) - 1)) {
1194 iPos += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan,
1195 *tableFrame, true);
1197 LogicalRect cRect(wm, iPos, 0, 0, rowRect.BSize(wm));
1199 // remember the last (iend-wards-most) column this cell spans into
1200 prevColIndex = cellColIndex + cellColSpan - 1;
1201 int32_t actualColSpan = cellColSpan;
1202 bool isVisible = false;
1203 for (int32_t colIdx = cellColIndex; actualColSpan > 0;
1204 colIdx++, actualColSpan--) {
1205 nsTableColFrame* colFrame = tableFrame->GetColFrame(colIdx);
1206 const nsStyleVisibility* colVis = colFrame->StyleVisibility();
1207 bool collapseCol = StyleVisibility::Collapse == colVis->mVisible;
1208 nsIFrame* cgFrame = colFrame->GetParent();
1209 const nsStyleVisibility* groupVis = cgFrame->StyleVisibility();
1210 bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
1211 bool isCollapsed = collapseCol || collapseGroup;
1212 if (!isCollapsed) {
1213 cRect.ISize(wm) += fifTable->GetColumnISizeFromFirstInFlow(colIdx);
1214 isVisible = true;
1215 if ((actualColSpan > 1)) {
1216 nsTableColFrame* nextColFrame =
1217 tableFrame->GetColFrame(colIdx + 1);
1218 const nsStyleVisibility* nextColVis =
1219 nextColFrame->StyleVisibility();
1220 if (StyleVisibility::Collapse != nextColVis->mVisible &&
1221 tableFrame->ColumnHasCellSpacingBefore(colIdx + 1)) {
1222 cRect.ISize(wm) += tableFrame->GetColSpacing(cellColIndex);
1227 iPos += cRect.ISize(wm);
1228 if (isVisible) {
1229 iPos += tableFrame->GetColSpacing(cellColIndex);
1231 int32_t actualRowSpan = tableFrame->GetEffectiveRowSpan(*cellFrame);
1232 nsTableRowFrame* rowFrame = GetNextRow();
1233 for (actualRowSpan--; actualRowSpan > 0 && rowFrame; actualRowSpan--) {
1234 const nsStyleVisibility* nextRowVis = rowFrame->StyleVisibility();
1235 bool collapseNextRow =
1236 StyleVisibility::Collapse == nextRowVis->mVisible;
1237 if (!collapseNextRow) {
1238 LogicalRect nextRect = rowFrame->GetLogicalRect(wm, containerSize);
1239 cRect.BSize(wm) +=
1240 nextRect.BSize(wm) +
1241 tableFrame->GetRowSpacing(rowFrame->GetRowIndex());
1243 rowFrame = rowFrame->GetNextRow();
1246 nsRect oldCellRect = cellFrame->GetRect();
1247 LogicalPoint oldCellNormalPos =
1248 cellFrame->GetLogicalNormalPosition(wm, containerSize);
1250 nsRect oldCellInkOverflow = cellFrame->InkOverflowRect();
1252 if (aRowOffset == 0 && cRect.Origin(wm) != oldCellNormalPos) {
1253 // We're moving the cell. Invalidate the old overflow area
1254 cellFrame->InvalidateFrameSubtree();
1257 cellFrame->MovePositionBy(wm, cRect.Origin(wm) - oldCellNormalPos);
1258 cellFrame->SetSize(wm, cRect.Size(wm));
1260 // XXXbz This looks completely bogus in the cases when we didn't
1261 // collapse the cell!
1262 LogicalRect cellBounds(wm, 0, 0, cRect.ISize(wm), cRect.BSize(wm));
1263 nsRect cellPhysicalBounds =
1264 cellBounds.GetPhysicalRect(wm, containerSize);
1265 OverflowAreas cellOverflow(cellPhysicalBounds, cellPhysicalBounds);
1266 cellFrame->FinishAndStoreOverflow(cellOverflow,
1267 cRect.Size(wm).GetPhysicalSize(wm));
1268 nsTableFrame::RePositionViews(cellFrame);
1269 ConsiderChildOverflow(overflow, cellFrame);
1271 if (aRowOffset == 0) {
1272 nsTableFrame::InvalidateTableFrame(cellFrame, oldCellRect,
1273 oldCellInkOverflow, false);
1279 SetRect(wm, rowRect, containerSize);
1280 overflow.UnionAllWith(nsRect(0, 0, rowRect.Width(wm), rowRect.Height(wm)));
1281 FinishAndStoreOverflow(overflow, rowRect.Size(wm).GetPhysicalSize(wm));
1283 nsTableFrame::RePositionViews(this);
1284 nsTableFrame::InvalidateTableFrame(this, oldRect, oldInkOverflow, false);
1285 return shift;
1289 * The following method is called by the row group frame's SplitRowGroup()
1290 * when it creates a continuing cell frame and wants to insert it into the
1291 * row's child list.
1293 void nsTableRowFrame::InsertCellFrame(nsTableCellFrame* aFrame,
1294 int32_t aColIndex) {
1295 // Find the cell frame where col index < aColIndex
1296 nsTableCellFrame* priorCell = nullptr;
1297 for (nsIFrame* child : mFrames) {
1298 nsTableCellFrame* cellFrame = do_QueryFrame(child);
1299 if (cellFrame) {
1300 uint32_t colIndex = cellFrame->ColIndex();
1301 // Can aColIndex be -1 here? Let's assume it can for now.
1302 if (static_cast<int32_t>(colIndex) < aColIndex) {
1303 priorCell = cellFrame;
1304 } else
1305 break;
1308 mFrames.InsertFrame(this, priorCell, aFrame);
1311 nsTableRowFrame* nsTableRowFrame::GetNextRow() const {
1312 nsIFrame* childFrame = GetNextSibling();
1313 while (childFrame) {
1314 nsTableRowFrame* rowFrame = do_QueryFrame(childFrame);
1315 if (rowFrame) {
1316 NS_ASSERTION(mozilla::StyleDisplay::TableRow ==
1317 childFrame->StyleDisplay()->mDisplay,
1318 "wrong display type on rowframe");
1319 return rowFrame;
1321 childFrame = childFrame->GetNextSibling();
1323 return nullptr;
1326 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(RowUnpaginatedHeightProperty, nscoord)
1328 void nsTableRowFrame::SetUnpaginatedBSize(nsPresContext* aPresContext,
1329 nscoord aValue) {
1330 NS_ASSERTION(!GetPrevInFlow(), "program error");
1331 // Set the property
1332 SetProperty(RowUnpaginatedHeightProperty(), aValue);
1335 nscoord nsTableRowFrame::GetUnpaginatedBSize() {
1336 return GetProperty(RowUnpaginatedHeightProperty());
1339 void nsTableRowFrame::SetContinuousBCBorderWidth(LogicalSide aForSide,
1340 BCPixelSize aPixelValue) {
1341 switch (aForSide) {
1342 case eLogicalSideIEnd:
1343 mIEndContBorderWidth = aPixelValue;
1344 return;
1345 case eLogicalSideBStart:
1346 mBStartContBorderWidth = aPixelValue;
1347 return;
1348 case eLogicalSideIStart:
1349 mIStartContBorderWidth = aPixelValue;
1350 return;
1351 default:
1352 NS_ERROR("invalid LogicalSide arg");
1355 #ifdef ACCESSIBILITY
1356 a11y::AccType nsTableRowFrame::AccessibleType() {
1357 return a11y::eHTMLTableRowType;
1359 #endif
1361 * Sets the NS_ROW_HAS_CELL_WITH_STYLE_BSIZE bit to indicate whether
1362 * this row has any cells that have non-auto-bsize. (Row-spanning
1363 * cells are ignored.)
1365 void nsTableRowFrame::InitHasCellWithStyleBSize(nsTableFrame* aTableFrame) {
1366 WritingMode wm = GetWritingMode();
1368 for (nsIFrame* kidFrame : mFrames) {
1369 nsTableCellFrame* cellFrame = do_QueryFrame(kidFrame);
1370 if (!cellFrame) {
1371 MOZ_ASSERT_UNREACHABLE("Table row has a non-cell child.");
1372 continue;
1374 // Ignore row-spanning cells
1375 const auto& cellBSize = cellFrame->StylePosition()->BSize(wm);
1376 if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 &&
1377 !cellBSize.IsAuto() &&
1378 /* calc() with both percentages and lengths treated like 'auto' */
1379 (cellBSize.ConvertsToLength() || cellBSize.ConvertsToPercentage())) {
1380 AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE);
1381 return;
1384 RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE);
1387 void nsTableRowFrame::InvalidateFrame(uint32_t aDisplayItemKey,
1388 bool aRebuildDisplayItems) {
1389 nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
1390 if (GetTableFrame()->IsBorderCollapse()) {
1391 const bool rebuild = StaticPrefs::layout_display_list_retain_sc();
1392 GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
1393 aDisplayItemKey, rebuild);
1397 void nsTableRowFrame::InvalidateFrameWithRect(const nsRect& aRect,
1398 uint32_t aDisplayItemKey,
1399 bool aRebuildDisplayItems) {
1400 nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
1401 aRebuildDisplayItems);
1402 // If we have filters applied that would affects our bounds, then
1403 // we get an inactive layer created and this is computed
1404 // within FrameLayerBuilder
1405 GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey,
1406 aRebuildDisplayItems);
1409 /* ----- global methods ----- */
1411 nsTableRowFrame* NS_NewTableRowFrame(PresShell* aPresShell,
1412 ComputedStyle* aStyle) {
1413 return new (aPresShell) nsTableRowFrame(aStyle, aPresShell->GetPresContext());
1416 NS_IMPL_FRAMEARENA_HELPERS(nsTableRowFrame)
1418 #ifdef DEBUG_FRAME_DUMP
1419 nsresult nsTableRowFrame::GetFrameName(nsAString& aResult) const {
1420 return MakeFrameName(u"TableRow"_ns, aResult);
1422 #endif