forked from mirrors/gecko-dev
		
	 4d57ea669d
			
		
	
	
		4d57ea669d
		
	
	
	
	
		
			
			Backed out changeset e862b1560caa (bug 1809568) Backed out changeset 0a20b91de67b (bug 1809568) Backed out changeset 6060fc7d49d4 (bug 1809568)
		
			
				
	
	
		
			1348 lines
		
	
	
	
		
			56 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1348 lines
		
	
	
	
		
			56 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /* rendering object for css3 multi-column layout */
 | |
| 
 | |
| #include "nsColumnSetFrame.h"
 | |
| 
 | |
| #include "mozilla/ColumnUtils.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/StaticPrefs_layout.h"
 | |
| #include "mozilla/ToString.h"
 | |
| #include "nsCSSRendering.h"
 | |
| #include "nsDisplayList.h"
 | |
| #include "nsIFrameInlines.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::layout;
 | |
| 
 | |
| // To see this log, use $ MOZ_LOG=ColumnSet:4 ./mach run
 | |
| static LazyLogModule sColumnSetLog("ColumnSet");
 | |
| #define COLUMN_SET_LOG(msg, ...) \
 | |
|   MOZ_LOG(sColumnSetLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
 | |
| 
 | |
| class nsDisplayColumnRule : public nsPaintedDisplayItem {
 | |
|  public:
 | |
|   nsDisplayColumnRule(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
 | |
|       : nsPaintedDisplayItem(aBuilder, aFrame) {
 | |
|     MOZ_COUNT_CTOR(nsDisplayColumnRule);
 | |
|   }
 | |
|   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayColumnRule)
 | |
| 
 | |
|   nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
 | |
|     *aSnap = false;
 | |
|     // We just return the frame's ink-overflow rect, which is guaranteed to
 | |
|     // contain all the column-rule areas.  It's not worth calculating the exact
 | |
|     // union of those areas since it would only lead to performance improvements
 | |
|     // during painting in rare edge cases.
 | |
|     return mFrame->InkOverflowRect() + ToReferenceFrame();
 | |
|   }
 | |
| 
 | |
|   bool CreateWebRenderCommands(
 | |
|       mozilla::wr::DisplayListBuilder& aBuilder,
 | |
|       mozilla::wr::IpcResourceUpdateQueue& aResources,
 | |
|       const StackingContextHelper& aSc,
 | |
|       mozilla::layers::RenderRootStateManager* aManager,
 | |
|       nsDisplayListBuilder* aDisplayListBuilder) override;
 | |
|   void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
 | |
| 
 | |
|   NS_DISPLAY_DECL_NAME("ColumnRule", TYPE_COLUMN_RULE);
 | |
| 
 | |
|  private:
 | |
|   nsTArray<nsCSSBorderRenderer> mBorderRenderers;
 | |
| };
 | |
| 
 | |
| void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder,
 | |
|                                 gfxContext* aCtx) {
 | |
|   static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
 | |
|       mBorderRenderers, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame());
 | |
| 
 | |
|   for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end();
 | |
|        iter++) {
 | |
|     iter->DrawBorders();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsDisplayColumnRule::CreateWebRenderCommands(
 | |
|     mozilla::wr::DisplayListBuilder& aBuilder,
 | |
|     mozilla::wr::IpcResourceUpdateQueue& aResources,
 | |
|     const StackingContextHelper& aSc,
 | |
|     mozilla::layers::RenderRootStateManager* aManager,
 | |
|     nsDisplayListBuilder* aDisplayListBuilder) {
 | |
|   RefPtr dt = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
 | |
|   if (!dt || !dt->IsValid()) {
 | |
|     return false;
 | |
|   }
 | |
|   gfxContext screenRefCtx(dt);
 | |
| 
 | |
|   bool dummy;
 | |
|   static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
 | |
|       mBorderRenderers, &screenRefCtx, GetBounds(aDisplayListBuilder, &dummy),
 | |
|       ToReferenceFrame());
 | |
| 
 | |
|   if (mBorderRenderers.IsEmpty()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   for (auto& renderer : mBorderRenderers) {
 | |
|     renderer.CreateWebRenderCommands(this, aBuilder, aResources, aSc);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Tracking issues:
 | |
|  *
 | |
|  * XXX cursor movement around the top and bottom of colums seems to make the
 | |
|  * editor lose the caret.
 | |
|  *
 | |
|  * XXX should we support CSS columns applied to table elements?
 | |
|  */
 | |
| nsContainerFrame* NS_NewColumnSetFrame(PresShell* aPresShell,
 | |
|                                        ComputedStyle* aStyle,
 | |
|                                        nsFrameState aStateFlags) {
 | |
|   nsColumnSetFrame* it =
 | |
|       new (aPresShell) nsColumnSetFrame(aStyle, aPresShell->GetPresContext());
 | |
|   it->AddStateBits(aStateFlags);
 | |
|   return it;
 | |
| }
 | |
| 
 | |
| NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
 | |
| 
 | |
| nsColumnSetFrame::nsColumnSetFrame(ComputedStyle* aStyle,
 | |
|                                    nsPresContext* aPresContext)
 | |
|     : nsContainerFrame(aStyle, aPresContext, kClassID),
 | |
|       mLastBalanceBSize(NS_UNCONSTRAINEDSIZE) {}
 | |
| 
 | |
| void nsColumnSetFrame::ForEachColumnRule(
 | |
|     const std::function<void(const nsRect& lineRect)>& aSetLineRect,
 | |
|     const nsPoint& aPt) const {
 | |
|   nsIFrame* child = mFrames.FirstChild();
 | |
|   if (!child) return;  // no columns
 | |
| 
 | |
|   nsIFrame* nextSibling = child->GetNextSibling();
 | |
|   if (!nextSibling) return;  // 1 column only - this means no gap to draw on
 | |
| 
 | |
|   const nsStyleColumn* colStyle = StyleColumn();
 | |
|   nscoord ruleWidth = colStyle->GetColumnRuleWidth();
 | |
|   if (!ruleWidth) return;
 | |
| 
 | |
|   WritingMode wm = GetWritingMode();
 | |
|   bool isVertical = wm.IsVertical();
 | |
|   bool isRTL = wm.IsBidiRTL();
 | |
| 
 | |
|   nsRect contentRect = GetContentRectRelativeToSelf() + aPt;
 | |
|   nsSize ruleSize = isVertical ? nsSize(contentRect.width, ruleWidth)
 | |
|                                : nsSize(ruleWidth, contentRect.height);
 | |
| 
 | |
|   while (nextSibling) {
 | |
|     // The frame tree goes RTL in RTL.
 | |
|     // The |prevFrame| and |nextFrame| frames here are the visually preceding
 | |
|     // (left/above) and following (right/below) frames, not in logical writing-
 | |
|     // mode direction.
 | |
|     nsIFrame* prevFrame = isRTL ? nextSibling : child;
 | |
|     nsIFrame* nextFrame = isRTL ? child : nextSibling;
 | |
| 
 | |
|     // Each child frame's position coordinates is actually relative to this
 | |
|     // nsColumnSetFrame.
 | |
|     // linePt will be at the top-left edge to paint the line.
 | |
|     nsPoint linePt;
 | |
|     if (isVertical) {
 | |
|       nscoord edgeOfPrev = prevFrame->GetRect().YMost() + aPt.y;
 | |
|       nscoord edgeOfNext = nextFrame->GetRect().Y() + aPt.y;
 | |
|       linePt = nsPoint(contentRect.x,
 | |
|                        (edgeOfPrev + edgeOfNext - ruleSize.height) / 2);
 | |
|     } else {
 | |
|       nscoord edgeOfPrev = prevFrame->GetRect().XMost() + aPt.x;
 | |
|       nscoord edgeOfNext = nextFrame->GetRect().X() + aPt.x;
 | |
|       linePt = nsPoint((edgeOfPrev + edgeOfNext - ruleSize.width) / 2,
 | |
|                        contentRect.y);
 | |
|     }
 | |
| 
 | |
|     aSetLineRect(nsRect(linePt, ruleSize));
 | |
| 
 | |
|     child = nextSibling;
 | |
|     nextSibling = nextSibling->GetNextSibling();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::CreateBorderRenderers(
 | |
|     nsTArray<nsCSSBorderRenderer>& aBorderRenderers, gfxContext* aCtx,
 | |
|     const nsRect& aDirtyRect, const nsPoint& aPt) {
 | |
|   WritingMode wm = GetWritingMode();
 | |
|   bool isVertical = wm.IsVertical();
 | |
|   const nsStyleColumn* colStyle = StyleColumn();
 | |
|   StyleBorderStyle ruleStyle;
 | |
| 
 | |
|   // Per spec, inset => ridge and outset => groove
 | |
|   if (colStyle->mColumnRuleStyle == StyleBorderStyle::Inset)
 | |
|     ruleStyle = StyleBorderStyle::Ridge;
 | |
|   else if (colStyle->mColumnRuleStyle == StyleBorderStyle::Outset)
 | |
|     ruleStyle = StyleBorderStyle::Groove;
 | |
|   else
 | |
|     ruleStyle = colStyle->mColumnRuleStyle;
 | |
| 
 | |
|   nscoord ruleWidth = colStyle->GetColumnRuleWidth();
 | |
|   if (!ruleWidth) return;
 | |
| 
 | |
|   aBorderRenderers.Clear();
 | |
|   nscolor ruleColor =
 | |
|       GetVisitedDependentColor(&nsStyleColumn::mColumnRuleColor);
 | |
| 
 | |
|   nsPresContext* presContext = PresContext();
 | |
|   // In order to re-use a large amount of code, we treat the column rule as a
 | |
|   // border. We create a new border style object and fill in all the details of
 | |
|   // the column rule as the left border. PaintBorder() does all the rendering
 | |
|   // for us, so we not only save an enormous amount of code but we'll support
 | |
|   // all the line styles that we support on borders!
 | |
|   nsStyleBorder border(*presContext->Document());
 | |
|   Sides skipSides;
 | |
|   if (isVertical) {
 | |
|     border.SetBorderWidth(eSideTop, ruleWidth);
 | |
|     border.SetBorderStyle(eSideTop, ruleStyle);
 | |
|     border.mBorderTopColor = StyleColor::FromColor(ruleColor);
 | |
|     skipSides |= mozilla::SideBits::eLeftRight;
 | |
|     skipSides |= mozilla::SideBits::eBottom;
 | |
|   } else {
 | |
|     border.SetBorderWidth(eSideLeft, ruleWidth);
 | |
|     border.SetBorderStyle(eSideLeft, ruleStyle);
 | |
|     border.mBorderLeftColor = StyleColor::FromColor(ruleColor);
 | |
|     skipSides |= mozilla::SideBits::eTopBottom;
 | |
|     skipSides |= mozilla::SideBits::eRight;
 | |
|   }
 | |
|   // If we use box-decoration-break: slice (the default), the border
 | |
|   // renderers will require clipping if we have continuations (see the
 | |
|   // aNeedsClip parameter to ConstructBorderRenderer in nsCSSRendering).
 | |
|   //
 | |
|   // Since it doesn't matter which box-decoration-break we use since
 | |
|   // we're only drawing borders (and not border-images), use 'clone'.
 | |
|   border.mBoxDecorationBreak = StyleBoxDecorationBreak::Clone;
 | |
| 
 | |
|   ForEachColumnRule(
 | |
|       [&](const nsRect& aLineRect) {
 | |
|         // Assert that we're not drawing a border-image here; if we were, we
 | |
|         // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
 | |
|         // returns.
 | |
|         MOZ_ASSERT(border.mBorderImageSource.IsNone());
 | |
| 
 | |
|         gfx::DrawTarget* dt = aCtx ? aCtx->GetDrawTarget() : nullptr;
 | |
|         bool borderIsEmpty = false;
 | |
|         Maybe<nsCSSBorderRenderer> br =
 | |
|             nsCSSRendering::CreateBorderRendererWithStyleBorder(
 | |
|                 presContext, dt, this, aDirtyRect, aLineRect, border, Style(),
 | |
|                 &borderIsEmpty, skipSides);
 | |
|         if (br.isSome()) {
 | |
|           MOZ_ASSERT(!borderIsEmpty);
 | |
|           aBorderRenderers.AppendElement(br.value());
 | |
|         }
 | |
|       },
 | |
|       aPt);
 | |
| }
 | |
| 
 | |
| static uint32_t ColumnBalancingDepth(const ReflowInput& aReflowInput,
 | |
|                                      uint32_t aMaxDepth) {
 | |
|   uint32_t depth = 0;
 | |
|   for (const ReflowInput* ri = aReflowInput.mParentReflowInput;
 | |
|        ri && depth < aMaxDepth; ri = ri->mParentReflowInput) {
 | |
|     if (ri->mFlags.mIsColumnBalancing) {
 | |
|       ++depth;
 | |
|     }
 | |
|   }
 | |
|   return depth;
 | |
| }
 | |
| 
 | |
| nsColumnSetFrame::ReflowConfig nsColumnSetFrame::ChooseColumnStrategy(
 | |
|     const ReflowInput& aReflowInput, bool aForceAuto = false) const {
 | |
|   const nsStyleColumn* colStyle = StyleColumn();
 | |
|   nscoord availContentISize = aReflowInput.AvailableISize();
 | |
|   if (aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE) {
 | |
|     availContentISize = aReflowInput.ComputedISize();
 | |
|   }
 | |
| 
 | |
|   nscoord colBSize = aReflowInput.AvailableBSize();
 | |
|   nscoord colGap =
 | |
|       ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize());
 | |
|   int32_t numColumns = colStyle->mColumnCount;
 | |
| 
 | |
|   // If column-fill is set to 'balance' or we have a column-span sibling, then
 | |
|   // we want to balance the columns.
 | |
|   bool isBalancing = (colStyle->mColumnFill == StyleColumnFill::Balance ||
 | |
|                       HasColumnSpanSiblings()) &&
 | |
|                      !aForceAuto;
 | |
|   if (isBalancing) {
 | |
|     const uint32_t kMaxNestedColumnBalancingDepth = 2;
 | |
|     const uint32_t balancingDepth =
 | |
|         ColumnBalancingDepth(aReflowInput, kMaxNestedColumnBalancingDepth);
 | |
|     if (balancingDepth == kMaxNestedColumnBalancingDepth) {
 | |
|       isBalancing = false;
 | |
|       numColumns = 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nscoord colISize;
 | |
|   // In vertical writing-mode, "column-width" (inline size) will actually be
 | |
|   // physical height, but its CSS name is still column-width.
 | |
|   if (colStyle->mColumnWidth.IsLength()) {
 | |
|     colISize =
 | |
|         ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
 | |
|     NS_ASSERTION(colISize >= 0, "negative column width");
 | |
|     // Reduce column count if necessary to make columns fit in the
 | |
|     // available width. Compute max number of columns that fit in
 | |
|     // availContentISize, satisfying colGap*(maxColumns - 1) +
 | |
|     // colISize*maxColumns <= availContentISize
 | |
|     if (availContentISize != NS_UNCONSTRAINEDSIZE && colGap + colISize > 0 &&
 | |
|         numColumns > 0) {
 | |
|       // This expression uses truncated rounding, which is what we
 | |
|       // want
 | |
|       int32_t maxColumns =
 | |
|           std::min(nscoord(nsStyleColumn::kMaxColumnCount),
 | |
|                    (availContentISize + colGap) / (colGap + colISize));
 | |
|       numColumns = std::max(1, std::min(numColumns, maxColumns));
 | |
|     }
 | |
|   } else if (numColumns > 0 && availContentISize != NS_UNCONSTRAINEDSIZE) {
 | |
|     nscoord iSizeMinusGaps = availContentISize - colGap * (numColumns - 1);
 | |
|     colISize = iSizeMinusGaps / numColumns;
 | |
|   } else {
 | |
|     colISize = NS_UNCONSTRAINEDSIZE;
 | |
|   }
 | |
|   // Take care of the situation where there's only one column but it's
 | |
|   // still too wide
 | |
|   colISize = std::max(1, std::min(colISize, availContentISize));
 | |
| 
 | |
|   nscoord expectedISizeLeftOver = 0;
 | |
| 
 | |
|   if (colISize != NS_UNCONSTRAINEDSIZE &&
 | |
|       availContentISize != NS_UNCONSTRAINEDSIZE) {
 | |
|     // distribute leftover space
 | |
| 
 | |
|     // First, determine how many columns will be showing if the column
 | |
|     // count is auto
 | |
|     if (numColumns <= 0) {
 | |
|       // choose so that colGap*(nominalColumnCount - 1) +
 | |
|       // colISize*nominalColumnCount is nearly availContentISize
 | |
|       // make sure to round down
 | |
|       if (colGap + colISize > 0) {
 | |
|         numColumns = (availContentISize + colGap) / (colGap + colISize);
 | |
|         // The number of columns should never exceed kMaxColumnCount.
 | |
|         numColumns =
 | |
|             std::min(nscoord(nsStyleColumn::kMaxColumnCount), numColumns);
 | |
|       }
 | |
|       if (numColumns <= 0) {
 | |
|         numColumns = 1;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Compute extra space and divide it among the columns
 | |
|     nscoord extraSpace =
 | |
|         std::max(0, availContentISize -
 | |
|                         (colISize * numColumns + colGap * (numColumns - 1)));
 | |
|     nscoord extraToColumns = extraSpace / numColumns;
 | |
|     colISize += extraToColumns;
 | |
|     expectedISizeLeftOver = extraSpace - (extraToColumns * numColumns);
 | |
|   }
 | |
| 
 | |
|   if (isBalancing) {
 | |
|     if (numColumns <= 0) {
 | |
|       // Hmm, auto column count, column width or available width is unknown,
 | |
|       // and balancing is required. Let's just use one column then.
 | |
|       numColumns = 1;
 | |
|     }
 | |
|     colBSize = std::min(mLastBalanceBSize, colBSize);
 | |
|   } else {
 | |
|     // CSS Fragmentation spec says, "To guarantee progress, fragmentainers are
 | |
|     // assumed to have a minimum block size of 1px regardless of their used
 | |
|     // size." https://drafts.csswg.org/css-break/#breaking-rules
 | |
|     //
 | |
|     // Note: we don't enforce the minimum block-size during balancing because
 | |
|     // this affects the result. If a balancing column container or its
 | |
|     // next-in-flows has zero block-size, it eventually gives up balancing, and
 | |
|     // ends up here.
 | |
|     colBSize = std::max(colBSize, nsPresContext::CSSPixelsToAppUnits(1));
 | |
|   }
 | |
| 
 | |
|   ReflowConfig config;
 | |
|   config.mUsedColCount = numColumns;
 | |
|   config.mColISize = colISize;
 | |
|   config.mExpectedISizeLeftOver = expectedISizeLeftOver;
 | |
|   config.mColGap = colGap;
 | |
|   config.mColBSize = colBSize;
 | |
|   config.mIsBalancing = isBalancing;
 | |
|   config.mForceAuto = aForceAuto;
 | |
|   config.mKnownFeasibleBSize = NS_UNCONSTRAINEDSIZE;
 | |
|   config.mKnownInfeasibleBSize = 0;
 | |
| 
 | |
|   COLUMN_SET_LOG(
 | |
|       "%s: this=%p, mUsedColCount=%d, mColISize=%d, "
 | |
|       "mExpectedISizeLeftOver=%d, mColGap=%d, mColBSize=%d, mIsBalancing=%d",
 | |
|       __func__, this, config.mUsedColCount, config.mColISize,
 | |
|       config.mExpectedISizeLeftOver, config.mColGap, config.mColBSize,
 | |
|       config.mIsBalancing);
 | |
| 
 | |
|   return config;
 | |
| }
 | |
| 
 | |
| static void MarkPrincipalChildrenDirty(nsIFrame* aFrame) {
 | |
|   for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
 | |
|     childFrame->MarkSubtreeDirty();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void MoveChildTo(nsIFrame* aChild, LogicalPoint aOrigin, WritingMode aWM,
 | |
|                         const nsSize& aContainerSize) {
 | |
|   if (aChild->GetLogicalPosition(aWM, aContainerSize) == aOrigin) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   aChild->SetPosition(aWM, aOrigin, aContainerSize);
 | |
|   nsContainerFrame::PlaceFrameView(aChild);
 | |
| }
 | |
| 
 | |
| nscoord nsColumnSetFrame::GetMinISize(gfxContext* aRenderingContext) {
 | |
|   nscoord iSize = 0;
 | |
|   DISPLAY_MIN_INLINE_SIZE(this, iSize);
 | |
| 
 | |
|   if (mFrames.FirstChild()) {
 | |
|     // We want to ignore this in the case that we're size contained
 | |
|     // because our children should not contribute to our
 | |
|     // intrinsic size.
 | |
|     iSize = mFrames.FirstChild()->GetMinISize(aRenderingContext);
 | |
|   }
 | |
|   const nsStyleColumn* colStyle = StyleColumn();
 | |
|   if (colStyle->mColumnWidth.IsLength()) {
 | |
|     nscoord colISize =
 | |
|         ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
 | |
|     // As available width reduces to zero, we reduce our number of columns
 | |
|     // to one, and don't enforce the column width, so just return the min
 | |
|     // of the child's min-width with any specified column width.
 | |
|     iSize = std::min(iSize, colISize);
 | |
|   } else {
 | |
|     NS_ASSERTION(colStyle->mColumnCount > 0,
 | |
|                  "column-count and column-width can't both be auto");
 | |
|     // As available width reduces to zero, we still have mColumnCount columns,
 | |
|     // so compute our minimum size based on the number of columns and their gaps
 | |
|     // and minimum per-column size.
 | |
|     nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
 | |
|     iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount, colGap, iSize);
 | |
|   }
 | |
|   // XXX count forced column breaks here? Maybe we should return the child's
 | |
|   // min-width times the minimum number of columns.
 | |
|   return iSize;
 | |
| }
 | |
| 
 | |
| nscoord nsColumnSetFrame::GetPrefISize(gfxContext* aRenderingContext) {
 | |
|   // Our preferred width is our desired column width, if specified, otherwise
 | |
|   // the child's preferred width, times the number of columns, plus the width
 | |
|   // of any required column gaps
 | |
|   // XXX what about forced column breaks here?
 | |
|   nscoord result = 0;
 | |
|   DISPLAY_PREF_INLINE_SIZE(this, result);
 | |
|   const nsStyleColumn* colStyle = StyleColumn();
 | |
| 
 | |
|   nscoord colISize;
 | |
|   if (colStyle->mColumnWidth.IsLength()) {
 | |
|     colISize =
 | |
|         ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
 | |
|   } else if (mFrames.FirstChild()) {
 | |
|     // We want to ignore this in the case that we're size contained
 | |
|     // because our children should not contribute to our
 | |
|     // intrinsic size.
 | |
|     colISize = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
 | |
|   } else {
 | |
|     colISize = 0;
 | |
|   }
 | |
| 
 | |
|   // If column-count is auto, assume one column.
 | |
|   uint32_t numColumns =
 | |
|       colStyle->mColumnCount == nsStyleColumn::kColumnCountAuto
 | |
|           ? 1
 | |
|           : colStyle->mColumnCount;
 | |
|   nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
 | |
|   result = ColumnUtils::IntrinsicISize(numColumns, colGap, colISize);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| nsColumnSetFrame::ColumnBalanceData nsColumnSetFrame::ReflowColumns(
 | |
|     ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
 | |
|     nsReflowStatus& aStatus, const ReflowConfig& aConfig,
 | |
|     bool aUnboundedLastColumn) {
 | |
|   ColumnBalanceData colData;
 | |
|   bool allFit = true;
 | |
|   WritingMode wm = GetWritingMode();
 | |
|   const bool isRTL = wm.IsBidiRTL();
 | |
|   const bool shrinkingBSize = mLastBalanceBSize > aConfig.mColBSize;
 | |
|   const bool changingBSize = mLastBalanceBSize != aConfig.mColBSize;
 | |
| 
 | |
|   COLUMN_SET_LOG(
 | |
|       "%s: Doing column reflow pass: mLastBalanceBSize=%d,"
 | |
|       " mColBSize=%d, RTL=%d, mUsedColCount=%d,"
 | |
|       " mColISize=%d, mColGap=%d",
 | |
|       __func__, mLastBalanceBSize, aConfig.mColBSize, isRTL,
 | |
|       aConfig.mUsedColCount, aConfig.mColISize, aConfig.mColGap);
 | |
| 
 | |
|   DrainOverflowColumns();
 | |
| 
 | |
|   if (changingBSize) {
 | |
|     mLastBalanceBSize = aConfig.mColBSize;
 | |
|     // XXX Seems like this could fire if incremental reflow pushed the column
 | |
|     // set down so we reflow incrementally with a different available height.
 | |
|     // We need a way to do an incremental reflow and be sure availableHeight
 | |
|     // changes are taken account of! Right now I think block frames with
 | |
|     // absolute children might exit early.
 | |
|     /*
 | |
|     NS_ASSERTION(
 | |
|         aKidReason != eReflowReason_Incremental,
 | |
|         "incremental reflow should not have changed the balance height");
 | |
|     */
 | |
|   }
 | |
| 
 | |
|   nsRect contentRect(0, 0, 0, 0);
 | |
|   OverflowAreas overflowRects;
 | |
| 
 | |
|   nsIFrame* child = mFrames.FirstChild();
 | |
|   LogicalPoint childOrigin(wm, 0, 0);
 | |
| 
 | |
|   // In vertical-rl mode, columns will not be correctly placed if the
 | |
|   // reflowInput's ComputedWidth() is UNCONSTRAINED (in which case we'll get
 | |
|   // a containerSize.width of zero here). In that case, the column positions
 | |
|   // will be adjusted later, after our correct contentSize is known.
 | |
|   //
 | |
|   // When column-span is enabled, containerSize.width is always constrained.
 | |
|   // However, for RTL, we need to adjust the column positions as well after our
 | |
|   // correct containerSize is known.
 | |
|   nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
 | |
| 
 | |
|   const nscoord computedBSize =
 | |
|       aReflowInput.mParentReflowInput->ComputedBSize();
 | |
|   nscoord contentBEnd = 0;
 | |
|   bool reflowNext = false;
 | |
| 
 | |
|   while (child) {
 | |
|     const bool reflowLastColumnWithUnconstrainedAvailBSize =
 | |
|         aUnboundedLastColumn && colData.mColCount == aConfig.mUsedColCount &&
 | |
|         aConfig.mIsBalancing;
 | |
| 
 | |
|     // We need to reflow the child (column) ...
 | |
|     bool reflowChild =
 | |
|         // if we are told to do so;
 | |
|         aReflowInput.ShouldReflowAllKids() ||
 | |
|         // if the child is dirty;
 | |
|         child->IsSubtreeDirty() ||
 | |
|         // if it's the last child because we need to obtain the block-end
 | |
|         // margin;
 | |
|         !child->GetNextSibling() ||
 | |
|         // if the next column is dirty, because the next column's first line(s)
 | |
|         // might be pullable back to this column;
 | |
|         child->GetNextSibling()->IsSubtreeDirty() ||
 | |
|         // if this is the last column and we are supposed to assign unbounded
 | |
|         // block-size to it, because that could change the available block-size
 | |
|         // from the last time we reflowed it and we should try to pull all the
 | |
|         // content from its next sibling (Note that it might be the last column,
 | |
|         // but not be the last child because the desired number of columns has
 | |
|         // changed.)
 | |
|         reflowLastColumnWithUnconstrainedAvailBSize;
 | |
| 
 | |
|     // If column-fill is auto (not the default), then we might need to
 | |
|     // move content between columns for any change in column block-size.
 | |
|     //
 | |
|     // The same is true if we have a non-'auto' computed block-size.
 | |
|     //
 | |
|     // FIXME: It's not clear to me why it's *ever* valid to have
 | |
|     // reflowChild be false when changingBSize is true, since it
 | |
|     // seems like a child broken over multiple columns might need to
 | |
|     // change the size of the fragment in each column.
 | |
|     if (!reflowChild && changingBSize &&
 | |
|         (StyleColumn()->mColumnFill == StyleColumnFill::Auto ||
 | |
|          computedBSize != NS_UNCONSTRAINEDSIZE)) {
 | |
|       reflowChild = true;
 | |
|     }
 | |
|     // If we need to pull up content from the prev-in-flow then this is not just
 | |
|     // a block-size shrink. The prev in flow will have set the dirty bit.
 | |
|     // Check the overflow rect YMost instead of just the child's content
 | |
|     // block-size. The child may have overflowing content that cares about the
 | |
|     // available block-size boundary. (It may also have overflowing content that
 | |
|     // doesn't care about the available block-size boundary, but if so, too bad,
 | |
|     // this optimization is defeated.) We want scrollable overflow here since
 | |
|     // this is a calculation that affects layout.
 | |
|     if (!reflowChild && shrinkingBSize) {
 | |
|       switch (wm.GetBlockDir()) {
 | |
|         case WritingMode::eBlockTB:
 | |
|           if (child->ScrollableOverflowRect().YMost() > aConfig.mColBSize) {
 | |
|             reflowChild = true;
 | |
|           }
 | |
|           break;
 | |
|         case WritingMode::eBlockLR:
 | |
|           if (child->ScrollableOverflowRect().XMost() > aConfig.mColBSize) {
 | |
|             reflowChild = true;
 | |
|           }
 | |
|           break;
 | |
|         case WritingMode::eBlockRL:
 | |
|           // XXX not sure how to handle this, so for now just don't attempt
 | |
|           // the optimization
 | |
|           reflowChild = true;
 | |
|           break;
 | |
|         default:
 | |
|           MOZ_ASSERT_UNREACHABLE("unknown block direction");
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     nscoord childContentBEnd = 0;
 | |
|     if (!reflowNext && !reflowChild) {
 | |
|       // This child does not need to be reflowed, but we may need to move it
 | |
|       MoveChildTo(child, childOrigin, wm, containerSize);
 | |
| 
 | |
|       // If this is the last frame then make sure we get the right status
 | |
|       nsIFrame* kidNext = child->GetNextSibling();
 | |
|       if (kidNext) {
 | |
|         aStatus.Reset();
 | |
|         if (kidNext->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
 | |
|           aStatus.SetOverflowIncomplete();
 | |
|         } else {
 | |
|           aStatus.SetIncomplete();
 | |
|         }
 | |
|       } else {
 | |
|         aStatus = mLastFrameStatus;
 | |
|       }
 | |
|       childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
 | |
| 
 | |
|       COLUMN_SET_LOG("%s: Skipping child #%d %p: status=%s", __func__,
 | |
|                      colData.mColCount, child, ToString(aStatus).c_str());
 | |
|     } else {
 | |
|       LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColBSize);
 | |
|       if (reflowLastColumnWithUnconstrainedAvailBSize) {
 | |
|         availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
 | |
| 
 | |
|         COLUMN_SET_LOG(
 | |
|             "%s: Reflowing last column with unconstrained block-size. Change "
 | |
|             "available block-size from %d to %d",
 | |
|             __func__, aConfig.mColBSize, availSize.BSize(wm));
 | |
|       }
 | |
| 
 | |
|       if (reflowNext) {
 | |
|         child->MarkSubtreeDirty();
 | |
|       }
 | |
| 
 | |
|       LogicalSize kidCBSize(wm, availSize.ISize(wm), computedBSize);
 | |
|       ReflowInput kidReflowInput(PresContext(), aReflowInput, child, availSize,
 | |
|                                  Some(kidCBSize));
 | |
|       kidReflowInput.mFlags.mIsTopOfPage = [&]() {
 | |
|         const bool isNestedMulticolOrPaginated =
 | |
|             aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits(
 | |
|                 NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
 | |
|             PresContext()->IsPaginated();
 | |
|         if (isNestedMulticolOrPaginated) {
 | |
|           if (aConfig.mForceAuto) {
 | |
|             // If we are forced to fill columns sequentially, force fit the
 | |
|             // content whether we are at top of page or not.
 | |
|             return true;
 | |
|           }
 | |
|           if (aReflowInput.mFlags.mIsTopOfPage) {
 | |
|             // If this is the last balancing reflow, we want to force fit
 | |
|             // content to avoid infinite loops.
 | |
|             return !aConfig.mIsBalancing || aConfig.mIsLastBalancingReflow;
 | |
|           }
 | |
|           // If we are a not at the top of page, we shouldn't force fit content.
 | |
|           // This is because our ColumnSetWrapperFrame can be pushed to the next
 | |
|           // column or page and reflowed again with a potentially larger
 | |
|           // available block-size.
 | |
|           return false;
 | |
|         }
 | |
|         // We are a top-level multicol in non-paginated context. Force fit the
 | |
|         // content only if we are not balancing columns.
 | |
|         return !aConfig.mIsBalancing;
 | |
|       }();
 | |
|       kidReflowInput.mFlags.mTableIsSplittable = false;
 | |
|       kidReflowInput.mFlags.mIsColumnBalancing = aConfig.mIsBalancing;
 | |
|       kidReflowInput.mBreakType = ReflowInput::BreakType::Column;
 | |
| 
 | |
|       // We need to reflow any float placeholders, even if our column block-size
 | |
|       // hasn't changed.
 | |
|       kidReflowInput.mFlags.mMustReflowPlaceholders = !changingBSize;
 | |
| 
 | |
|       COLUMN_SET_LOG(
 | |
|           "%s: Reflowing child #%d %p: availSize=(%d,%d), kidCBSize=(%d,%d), "
 | |
|           "child's mIsTopOfPage=%d",
 | |
|           __func__, colData.mColCount, child, availSize.ISize(wm),
 | |
|           availSize.BSize(wm), kidCBSize.ISize(wm), kidCBSize.BSize(wm),
 | |
|           kidReflowInput.mFlags.mIsTopOfPage);
 | |
| 
 | |
|       // Note if the column's next in flow is not being changed by this
 | |
|       // incremental reflow. This may allow the current column to avoid trying
 | |
|       // to pull lines from the next column.
 | |
|       if (child->GetNextSibling() && !HasAnyStateBits(NS_FRAME_IS_DIRTY) &&
 | |
|           !child->GetNextSibling()->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
 | |
|         kidReflowInput.mFlags.mNextInFlowUntouched = true;
 | |
|       }
 | |
| 
 | |
|       ReflowOutput kidDesiredSize(wm);
 | |
| 
 | |
|       // XXX it would be cool to consult the float manager for the
 | |
|       // previous block to figure out the region of floats from the
 | |
|       // previous column that extend into this column, and subtract
 | |
|       // that region from the new float manager.  So you could stick a
 | |
|       // really big float in the first column and text in following
 | |
|       // columns would flow around it.
 | |
| 
 | |
|       MOZ_ASSERT(kidReflowInput.ComputedLogicalMargin(wm).IsAllZero(),
 | |
|                  "-moz-column-content has no margin!");
 | |
|       aStatus.Reset();
 | |
|       ReflowChild(child, PresContext(), kidDesiredSize, kidReflowInput, wm,
 | |
|                   childOrigin, containerSize, ReflowChildFlags::Default,
 | |
|                   aStatus);
 | |
| 
 | |
|       if (colData.mColCount == 1 && aStatus.IsInlineBreakBefore()) {
 | |
|         COLUMN_SET_LOG("%s: Content in the first column reports break-before!",
 | |
|                        __func__);
 | |
|         allFit = false;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       reflowNext = aStatus.NextInFlowNeedsReflow();
 | |
| 
 | |
|       // The carried-out block-end margin of column content might be non-zero
 | |
|       // when we try to find the best column balancing block size, but it should
 | |
|       // never affect the size column set nor be further carried out. Set it to
 | |
|       // zero.
 | |
|       //
 | |
|       // FIXME: For some types of fragmentation, we should carry the margin into
 | |
|       // the next column. Also see
 | |
|       // https://drafts.csswg.org/css-break-4/#break-margins
 | |
|       //
 | |
|       // FIXME: This should never happen for the last column, since it should be
 | |
|       // a margin root; see nsBlockFrame::IsMarginRoot(). However, sometimes the
 | |
|       // last column has an empty continuation while searching for the best
 | |
|       // column balancing bsize, which prevents the last column from being a
 | |
|       // margin root.
 | |
|       kidDesiredSize.mCarriedOutBEndMargin.Zero();
 | |
| 
 | |
|       NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
 | |
| 
 | |
|       FinishReflowChild(child, PresContext(), kidDesiredSize, &kidReflowInput,
 | |
|                         wm, childOrigin, containerSize,
 | |
|                         ReflowChildFlags::Default);
 | |
| 
 | |
|       childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
 | |
|       if (childContentBEnd > aConfig.mColBSize) {
 | |
|         allFit = false;
 | |
|       }
 | |
|       if (childContentBEnd > availSize.BSize(wm)) {
 | |
|         colData.mMaxOverflowingBSize =
 | |
|             std::max(childContentBEnd, colData.mMaxOverflowingBSize);
 | |
|       }
 | |
| 
 | |
|       COLUMN_SET_LOG(
 | |
|           "%s: Reflowed child #%d %p: status=%s, desiredSize=(%d,%d), "
 | |
|           "childContentBEnd=%d, CarriedOutBEndMargin=%d (ignored)",
 | |
|           __func__, colData.mColCount, child, ToString(aStatus).c_str(),
 | |
|           kidDesiredSize.ISize(wm), kidDesiredSize.BSize(wm), childContentBEnd,
 | |
|           kidDesiredSize.mCarriedOutBEndMargin.get());
 | |
|     }
 | |
| 
 | |
|     contentRect.UnionRect(contentRect, child->GetRect());
 | |
| 
 | |
|     ConsiderChildOverflow(overflowRects, child);
 | |
|     contentBEnd = std::max(contentBEnd, childContentBEnd);
 | |
|     colData.mLastBSize = childContentBEnd;
 | |
|     colData.mSumBSize += childContentBEnd;
 | |
| 
 | |
|     // Build a continuation column if necessary
 | |
|     nsIFrame* kidNextInFlow = child->GetNextInFlow();
 | |
| 
 | |
|     if (aStatus.IsFullyComplete()) {
 | |
|       NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
 | |
|       child = nullptr;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // Make sure that the column has a next-in-flow. If not, we must
 | |
|     // create one to hold the overflowing stuff, even if we're just
 | |
|     // going to put it on our overflow list and let *our*
 | |
|     // next in flow handle it.
 | |
|     if (!kidNextInFlow) {
 | |
|       NS_ASSERTION(aStatus.NextInFlowNeedsReflow(),
 | |
|                    "We have to create a continuation, but the block doesn't "
 | |
|                    "want us to reflow it?");
 | |
| 
 | |
|       // We need to create a continuing column
 | |
|       kidNextInFlow = CreateNextInFlow(child);
 | |
|     }
 | |
| 
 | |
|     // Make sure we reflow a next-in-flow when it switches between being
 | |
|     // normal or overflow container
 | |
|     if (aStatus.IsOverflowIncomplete()) {
 | |
|       if (!kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
 | |
|         aStatus.SetNextInFlowNeedsReflow();
 | |
|         reflowNext = true;
 | |
|         kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
 | |
|       }
 | |
|     } else if (kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
 | |
|       aStatus.SetNextInFlowNeedsReflow();
 | |
|       reflowNext = true;
 | |
|       kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
 | |
|     }
 | |
| 
 | |
|     // We have reached the maximum number of columns. If we are balancing, stop
 | |
|     // this reflow and continue finding the optimal balancing block-size.
 | |
|     //
 | |
|     // Otherwise, i.e. we are not balancing, stop this reflow and let the parent
 | |
|     // of our multicol container create a next-in-flow if all of the following
 | |
|     // conditions are met.
 | |
|     //
 | |
|     // 1) We fill columns sequentially by the request of the style, not by our
 | |
|     // internal needs, i.e. aConfig.mForceAuto is false.
 | |
|     //
 | |
|     // We don't want to stop this reflow when we force fill the columns
 | |
|     // sequentially. We usually go into this mode when giving up balancing, and
 | |
|     // this is the last resort to fit all our children by creating overflow
 | |
|     // columns.
 | |
|     //
 | |
|     // 2) In a fragmented context, our multicol container still has block-size
 | |
|     // left for its next-in-flow, i.e.
 | |
|     // aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft is false.
 | |
|     //
 | |
|     // Note that in a continuous context, i.e. our multicol container's
 | |
|     // available block-size is unconstrained, if it has a fixed block-size
 | |
|     // mColumnSetWrapperHasNoBSizeLeft is always true because nothing stops it
 | |
|     // from applying all its block-size in the first-in-flow. Otherwise, i.e.
 | |
|     // our multicol container has an unconstrained block-size, we shouldn't be
 | |
|     // here because all our children should fit in the very first column even if
 | |
|     // mColumnSetWrapperHasNoBSizeLeft is false.
 | |
|     //
 | |
|     // According to the definition of mColumnSetWrapperHasNoBSizeLeft, if the
 | |
|     // bit is *not* set, either our multicol container has unconstrained
 | |
|     // block-size, or it has a constrained block-size and has block-size left
 | |
|     // for its next-in-flow. In either cases, the parent of our multicol
 | |
|     // container can create a next-in-flow for the container that guaranteed to
 | |
|     // have non-zero block-size for the container's children.
 | |
|     //
 | |
|     // Put simply, if either one of the above conditions is not met, we are
 | |
|     // going to create more overflow columns until all our children are fit.
 | |
|     if (colData.mColCount >= aConfig.mUsedColCount &&
 | |
|         (aConfig.mIsBalancing ||
 | |
|          (!aConfig.mForceAuto &&
 | |
|           !aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft))) {
 | |
|       NS_ASSERTION(aConfig.mIsBalancing ||
 | |
|                        aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
 | |
|                    "Why are we here if we have unlimited block-size to fill "
 | |
|                    "columns sequentially.");
 | |
| 
 | |
|       // No more columns allowed here. Stop.
 | |
|       aStatus.SetNextInFlowNeedsReflow();
 | |
|       kidNextInFlow->MarkSubtreeDirty();
 | |
|       // Move any of our leftover columns to our overflow list. Our
 | |
|       // next-in-flow will eventually pick them up.
 | |
|       nsFrameList continuationColumns = mFrames.TakeFramesAfter(child);
 | |
|       if (continuationColumns.NotEmpty()) {
 | |
|         SetOverflowFrames(std::move(continuationColumns));
 | |
|       }
 | |
|       child = nullptr;
 | |
| 
 | |
|       COLUMN_SET_LOG("%s: We are not going to create overflow columns.",
 | |
|                      __func__);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (PresContext()->HasPendingInterrupt()) {
 | |
|       // Stop the loop now while |child| still points to the frame that bailed
 | |
|       // out.  We could keep going here and condition a bunch of the code in
 | |
|       // this loop on whether there's an interrupt, or even just keep going and
 | |
|       // trying to reflow the blocks (even though we know they'll interrupt
 | |
|       // right after their first line), but stopping now is conceptually the
 | |
|       // simplest (and probably fastest) thing.
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // Advance to the next column
 | |
|     child = child->GetNextSibling();
 | |
|     ++colData.mColCount;
 | |
| 
 | |
|     if (child) {
 | |
|       childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap;
 | |
| 
 | |
|       COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__,
 | |
|                      childOrigin.I(wm));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (PresContext()->CheckForInterrupt(this) &&
 | |
|       HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
 | |
|     // Mark all our kids starting with |child| dirty
 | |
| 
 | |
|     // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
 | |
|     // because we might have interrupted while reflowing |child|, and since
 | |
|     // we're about to add a dirty bit to |child| we need to make sure that
 | |
|     // |this| is scheduled to have dirty bits marked on it and its ancestors.
 | |
|     // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
 | |
|     // bail out immediately, since it'll already have a dirty bit.
 | |
|     for (; child; child = child->GetNextSibling()) {
 | |
|       child->MarkSubtreeDirty();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   colData.mMaxBSize = contentBEnd;
 | |
|   LogicalSize contentSize = LogicalSize(wm, contentRect.Size());
 | |
|   contentSize.BSize(wm) = std::max(contentSize.BSize(wm), contentBEnd);
 | |
|   mLastFrameStatus = aStatus;
 | |
| 
 | |
|   if (computedBSize != NS_UNCONSTRAINEDSIZE && !HasColumnSpanSiblings()) {
 | |
|     NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
 | |
|                  "Available block-size should be constrained because it's "
 | |
|                  "restricted by the computed block-size when our reflow "
 | |
|                  "input is created in nsBlockFrame::ReflowBlockFrame()!");
 | |
| 
 | |
|     // If a) our parent ColumnSetWrapper has constrained block-size
 | |
|     // (nsBlockFrame::ReflowBlockFrame() applies the block-size constraint
 | |
|     // when creating a ReflowInput for ColumnSetFrame child); and b) we are the
 | |
|     // sole ColumnSet or the last ColumnSet continuation split by column-spans
 | |
|     // in a ColumnSetWrapper, extend our block-size to consume the available
 | |
|     // block-size so that the column-rules are drawn to the content block-end
 | |
|     // edge of the multicol container.
 | |
|     contentSize.BSize(wm) =
 | |
|         std::max(contentSize.BSize(wm), aReflowInput.AvailableBSize());
 | |
|   }
 | |
| 
 | |
|   aDesiredSize.SetSize(wm, contentSize);
 | |
|   aDesiredSize.mOverflowAreas = overflowRects;
 | |
|   aDesiredSize.UnionOverflowAreasWithDesiredBounds();
 | |
| 
 | |
|   // In vertical-rl mode, make a second pass if necessary to reposition the
 | |
|   // columns with the correct container width. (In other writing modes,
 | |
|   // correct containerSize was not required for column positioning so we don't
 | |
|   // need this fixup.)
 | |
|   //
 | |
|   // RTL column positions also depend on ColumnSet's actual contentSize. We need
 | |
|   // this fixup, too.
 | |
|   if ((wm.IsVerticalRL() || isRTL) &&
 | |
|       containerSize.width != contentSize.Width(wm)) {
 | |
|     const nsSize finalContainerSize = aDesiredSize.PhysicalSize();
 | |
|     OverflowAreas overflowRects;
 | |
|     for (nsIFrame* child : mFrames) {
 | |
|       // Get the logical position as set previously using a provisional or
 | |
|       // dummy containerSize, and reset with the correct container size.
 | |
|       child->SetPosition(wm, child->GetLogicalPosition(wm, containerSize),
 | |
|                          finalContainerSize);
 | |
|       ConsiderChildOverflow(overflowRects, child);
 | |
|     }
 | |
|     aDesiredSize.mOverflowAreas = overflowRects;
 | |
|     aDesiredSize.UnionOverflowAreasWithDesiredBounds();
 | |
|   }
 | |
| 
 | |
|   colData.mFeasible = allFit && aStatus.IsFullyComplete();
 | |
| 
 | |
|   COLUMN_SET_LOG(
 | |
|       "%s: Done column reflow pass: %s, mMaxBSize=%d, mSumBSize=%d, "
 | |
|       "mLastBSize=%d, mMaxOverflowingBSize=%d",
 | |
|       __func__, colData.mFeasible ? "Feasible :)" : "Infeasible :(",
 | |
|       colData.mMaxBSize, colData.mSumBSize, colData.mLastBSize,
 | |
|       colData.mMaxOverflowingBSize);
 | |
| 
 | |
|   return colData;
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::DrainOverflowColumns() {
 | |
|   // First grab the prev-in-flows overflows and reparent them to this
 | |
|   // frame.
 | |
|   nsPresContext* presContext = PresContext();
 | |
|   nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
 | |
|   if (prev) {
 | |
|     AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
 | |
|     if (overflows) {
 | |
|       nsContainerFrame::ReparentFrameViewList(*overflows, prev, this);
 | |
| 
 | |
|       mFrames.InsertFrames(this, nullptr, std::move(*overflows));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Now pull back our own overflows and append them to our children.
 | |
|   // We don't need to reparent them since we're already their parent.
 | |
|   AutoFrameListPtr overflows(presContext, StealOverflowFrames());
 | |
|   if (overflows) {
 | |
|     // We're already the parent for these frames, so no need to set
 | |
|     // their parent again.
 | |
|     mFrames.AppendFrames(nullptr, std::move(*overflows));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::FindBestBalanceBSize(const ReflowInput& aReflowInput,
 | |
|                                             nsPresContext* aPresContext,
 | |
|                                             ReflowConfig& aConfig,
 | |
|                                             ColumnBalanceData aColData,
 | |
|                                             ReflowOutput& aDesiredSize,
 | |
|                                             bool aUnboundedLastColumn,
 | |
|                                             nsReflowStatus& aStatus) {
 | |
|   MOZ_ASSERT(aConfig.mIsBalancing,
 | |
|              "Why are we here if we are not balancing columns?");
 | |
| 
 | |
|   const nscoord availableContentBSize = aReflowInput.AvailableBSize();
 | |
| 
 | |
|   // Termination of the algorithm below is guaranteed because
 | |
|   // aConfig.knownFeasibleBSize - aConfig.knownInfeasibleBSize decreases in
 | |
|   // every iteration.
 | |
|   int32_t iterationCount = 1;
 | |
| 
 | |
|   // We set this flag when we detect that we may contain a frame
 | |
|   // that can break anywhere (thus foiling the linear decrease-by-one
 | |
|   // search)
 | |
|   bool maybeContinuousBreakingDetected = false;
 | |
|   bool possibleOptimalBSizeDetected = false;
 | |
| 
 | |
|   // This is the extra block-size added to the optimal column block-size
 | |
|   // estimation which is calculated in the while-loop by dividing
 | |
|   // aColData.mSumBSize into N columns.
 | |
|   //
 | |
|   // The constant is arbitrary. We use a half of line-height first. In case a
 | |
|   // column container uses *zero* (or a very small) line-height, use a half of
 | |
|   // default line-height 1140/2 = 570 app units as the minimum value. Otherwise
 | |
|   // we might take more than necessary iterations before finding a feasible
 | |
|   // block-size.
 | |
|   nscoord extraBlockSize = std::max(570, aReflowInput.GetLineHeight() / 2);
 | |
| 
 | |
|   // We use divide-by-N to estimate the optimal column block-size only if the
 | |
|   // last column's available block-size is unbounded.
 | |
|   bool foundFeasibleBSizeCloserToBest = !aUnboundedLastColumn;
 | |
| 
 | |
|   // Stop the binary search when the difference of the feasible and infeasible
 | |
|   // block-size is within this gap. Here we use one device pixel.
 | |
|   const int32_t gapToStop = aPresContext->DevPixelsToAppUnits(1);
 | |
| 
 | |
|   while (!aPresContext->HasPendingInterrupt()) {
 | |
|     nscoord lastKnownFeasibleBSize = aConfig.mKnownFeasibleBSize;
 | |
| 
 | |
|     // Record what we learned from the last reflow
 | |
|     if (aColData.mFeasible) {
 | |
|       // mMaxBSize is feasible. Also, mLastBalanceBSize is feasible.
 | |
|       aConfig.mKnownFeasibleBSize =
 | |
|           std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
 | |
|       aConfig.mKnownFeasibleBSize =
 | |
|           std::min(aConfig.mKnownFeasibleBSize, mLastBalanceBSize);
 | |
| 
 | |
|       // Furthermore, no block-size less than the block-size of the last
 | |
|       // column can ever be feasible. (We might be able to reduce the
 | |
|       // block-size of a non-last column by moving content to a later column,
 | |
|       // but we can't do that with the last column.)
 | |
|       if (aColData.mColCount == aConfig.mUsedColCount) {
 | |
|         aConfig.mKnownInfeasibleBSize =
 | |
|             std::max(aConfig.mKnownInfeasibleBSize, aColData.mLastBSize - 1);
 | |
|       }
 | |
|     } else {
 | |
|       aConfig.mKnownInfeasibleBSize =
 | |
|           std::max(aConfig.mKnownInfeasibleBSize, mLastBalanceBSize);
 | |
| 
 | |
|       // If a column didn't fit in its available block-size, then its current
 | |
|       // block-size must be the minimum block-size for unbreakable content in
 | |
|       // the column, and therefore no smaller block-size can be feasible.
 | |
|       aConfig.mKnownInfeasibleBSize = std::max(
 | |
|           aConfig.mKnownInfeasibleBSize, aColData.mMaxOverflowingBSize - 1);
 | |
| 
 | |
|       if (aUnboundedLastColumn) {
 | |
|         // The last column is unbounded, so all content got reflowed, so the
 | |
|         // mMaxBSize is feasible.
 | |
|         aConfig.mKnownFeasibleBSize =
 | |
|             std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
 | |
| 
 | |
|         NS_ASSERTION(mLastFrameStatus.IsComplete(),
 | |
|                      "Last column should be complete if the available "
 | |
|                      "block-size is unconstrained!");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     COLUMN_SET_LOG(
 | |
|         "%s: this=%p, mKnownInfeasibleBSize=%d, mKnownFeasibleBSize=%d",
 | |
|         __func__, this, aConfig.mKnownInfeasibleBSize,
 | |
|         aConfig.mKnownFeasibleBSize);
 | |
| 
 | |
|     if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) {
 | |
|       // aConfig.mKnownFeasibleBSize is where we want to be. This can happen in
 | |
|       // the very first iteration when a column container solely has a tall
 | |
|       // unbreakable child that overflows the container.
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
 | |
|       // There's no feasible block-size to fit our contents. We may need to
 | |
|       // reflow one more time after this loop.
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     const nscoord gap =
 | |
|         aConfig.mKnownFeasibleBSize - aConfig.mKnownInfeasibleBSize;
 | |
|     if (gap <= gapToStop && possibleOptimalBSizeDetected) {
 | |
|       // We detected a possible optimal block-size in the last iteration. If it
 | |
|       // is infeasible, we may need to reflow one more time after this loop.
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) {
 | |
|       // We decreased the feasible block-size by one twip only. This could
 | |
|       // indicate that there is a continuously breakable child frame
 | |
|       // that we are crawling through.
 | |
|       maybeContinuousBreakingDetected = true;
 | |
|     }
 | |
| 
 | |
|     nscoord nextGuess = aConfig.mKnownInfeasibleBSize + gap / 2;
 | |
|     if (aConfig.mKnownFeasibleBSize - nextGuess < extraBlockSize &&
 | |
|         !maybeContinuousBreakingDetected) {
 | |
|       // We're close to our target, so just try shrinking just the
 | |
|       // minimum amount that will cause one of our columns to break
 | |
|       // differently.
 | |
|       nextGuess = aConfig.mKnownFeasibleBSize - 1;
 | |
|     } else if (!foundFeasibleBSizeCloserToBest) {
 | |
|       // Make a guess by dividing mSumBSize into N columns and adding
 | |
|       // extraBlockSize to try to make it on the feasible side.
 | |
|       nextGuess = aColData.mSumBSize / aConfig.mUsedColCount + extraBlockSize;
 | |
|       // Sanitize it
 | |
|       nextGuess = clamped(nextGuess, aConfig.mKnownInfeasibleBSize + 1,
 | |
|                           aConfig.mKnownFeasibleBSize - 1);
 | |
|       // We keep doubling extraBlockSize in every iteration until we find a
 | |
|       // feasible guess.
 | |
|       extraBlockSize *= 2;
 | |
|     } else if (aConfig.mKnownFeasibleBSize == NS_UNCONSTRAINEDSIZE) {
 | |
|       // This can happen when we had a next-in-flow so we didn't
 | |
|       // want to do an unbounded block-size measuring step. Let's just increase
 | |
|       // from the infeasible block-size by some reasonable amount.
 | |
|       nextGuess = aConfig.mKnownInfeasibleBSize * 2 + extraBlockSize;
 | |
|     } else if (gap <= gapToStop) {
 | |
|       // Floor nextGuess to the greatest multiple of gapToStop below or equal to
 | |
|       // mKnownFeasibleBSize.
 | |
|       nextGuess = aConfig.mKnownFeasibleBSize / gapToStop * gapToStop;
 | |
|       possibleOptimalBSizeDetected = true;
 | |
|     }
 | |
| 
 | |
|     // Don't bother guessing more than our block-size constraint.
 | |
|     nextGuess = std::min(availableContentBSize, nextGuess);
 | |
| 
 | |
|     COLUMN_SET_LOG("%s: Choosing next guess=%d, iteration=%d", __func__,
 | |
|                    nextGuess, iterationCount);
 | |
|     ++iterationCount;
 | |
| 
 | |
|     aConfig.mColBSize = nextGuess;
 | |
| 
 | |
|     aUnboundedLastColumn = false;
 | |
|     MarkPrincipalChildrenDirty(this);
 | |
|     aColData =
 | |
|         ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig, false);
 | |
| 
 | |
|     if (!foundFeasibleBSizeCloserToBest && aColData.mFeasible) {
 | |
|       foundFeasibleBSizeCloserToBest = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!aColData.mFeasible && !aPresContext->HasPendingInterrupt()) {
 | |
|     // We need to reflow one more time at the feasible block-size to
 | |
|     // get a valid layout.
 | |
|     if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
 | |
|       aConfig.mColBSize = availableContentBSize;
 | |
|       if (mLastBalanceBSize == availableContentBSize) {
 | |
|         // If we end up here, we have a constrained available content
 | |
|         // block-size, and our last column's block-size exceeds it. Also, if
 | |
|         // this is the first balancing iteration, the last column is given
 | |
|         // unconstrained available block-size, so it has a fully complete
 | |
|         // reflow status. Therefore, we always want to reflow again at the
 | |
|         // available content block-size to get a valid layout and a correct
 | |
|         // reflow status (likely an *incomplete* status) so that our column
 | |
|         // container can be fragmented if needed.
 | |
| 
 | |
|         if (aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft) {
 | |
|           // If our column container has a constrained block-size (either in a
 | |
|           // paginated context or in a nested column container), and is going
 | |
|           // to consume all its computed block-size in this fragment, then our
 | |
|           // column container has no block-size left to contain our
 | |
|           // next-in-flows. We have to give up balancing, and create our
 | |
|           // own overflow columns.
 | |
|           //
 | |
|           // We don't want to create overflow columns immediately when our
 | |
|           // content doesn't fit since this changes our reflow status from
 | |
|           // incomplete to complete. Valid reasons include 1) the outer column
 | |
|           // container might do column balancing, and it can enlarge the
 | |
|           // available content block-size so that the nested one could fit its
 | |
|           // content in next balancing iteration; or 2) the outer column
 | |
|           // container is filling columns sequentially, and may have more
 | |
|           // inline-size to create more column boxes for the nested column
 | |
|           // container's next-in-flows.
 | |
|           aConfig = ChooseColumnStrategy(aReflowInput, true);
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       aConfig.mColBSize = aConfig.mKnownFeasibleBSize;
 | |
|     }
 | |
| 
 | |
|     // This is our last attempt to reflow. If our column container's available
 | |
|     // block-size is unconstrained, make sure that the last column is
 | |
|     // allowed to have arbitrary block-size here, even though we were
 | |
|     // balancing. Otherwise we'd have to split, and it's not clear what we'd
 | |
|     // do with that.
 | |
|     COLUMN_SET_LOG("%s: Last attempt to call ReflowColumns", __func__);
 | |
|     aConfig.mIsLastBalancingReflow = true;
 | |
|     const bool forceUnboundedLastColumn =
 | |
|         aReflowInput.mParentReflowInput->AvailableBSize() ==
 | |
|         NS_UNCONSTRAINEDSIZE;
 | |
|     MarkPrincipalChildrenDirty(this);
 | |
|     ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig,
 | |
|                   forceUnboundedLastColumn);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
 | |
|                               ReflowOutput& aDesiredSize,
 | |
|                               const ReflowInput& aReflowInput,
 | |
|                               nsReflowStatus& aStatus) {
 | |
|   MarkInReflow();
 | |
|   // Don't support interruption in columns
 | |
|   nsPresContext::InterruptPreventer noInterrupts(aPresContext);
 | |
| 
 | |
|   DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
 | |
|   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
 | |
|   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | |
| 
 | |
|   MOZ_ASSERT(aReflowInput.mCBReflowInput->mFrame->StyleColumn()
 | |
|                  ->IsColumnContainerStyle(),
 | |
|              "The column container should have relevant column styles!");
 | |
|   MOZ_ASSERT(aReflowInput.mParentReflowInput->mFrame->IsColumnSetWrapperFrame(),
 | |
|              "The column container should be ColumnSetWrapperFrame!");
 | |
|   MOZ_ASSERT(
 | |
|       aReflowInput.ComputedLogicalBorderPadding(aReflowInput.GetWritingMode())
 | |
|           .IsAllZero(),
 | |
|       "Only the column container can have border and padding!");
 | |
|   MOZ_ASSERT(
 | |
|       GetChildList(FrameChildListID::OverflowContainers).IsEmpty() &&
 | |
|           GetChildList(FrameChildListID::ExcessOverflowContainers).IsEmpty(),
 | |
|       "ColumnSetFrame should store overflow containers in principal "
 | |
|       "child list!");
 | |
| 
 | |
|   //------------ Handle Incremental Reflow -----------------
 | |
| 
 | |
|   COLUMN_SET_LOG("%s: Begin Reflow: this=%p, is nested multicol=%d", __func__,
 | |
|                  this,
 | |
|                  aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits(
 | |
|                      NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
 | |
| 
 | |
|   // If inline size is unconstrained, set aForceAuto to true to allow
 | |
|   // the columns to expand in the inline direction. (This typically
 | |
|   // happens in orthogonal flows where the inline direction is the
 | |
|   // container's block direction).
 | |
|   ReflowConfig config = ChooseColumnStrategy(
 | |
|       aReflowInput, aReflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE);
 | |
| 
 | |
|   // If balancing, then we allow the last column to grow to unbounded
 | |
|   // block-size during the first reflow. This gives us a way to estimate
 | |
|   // what the average column block-size should be, because we can measure
 | |
|   // the block-size of all the columns and sum them up. But don't do this
 | |
|   // if we have a next in flow because we don't want to suck all its
 | |
|   // content back here and then have to push it out again!
 | |
|   nsIFrame* nextInFlow = GetNextInFlow();
 | |
|   bool unboundedLastColumn = config.mIsBalancing && !nextInFlow;
 | |
|   const ColumnBalanceData colData = ReflowColumns(
 | |
|       aDesiredSize, aReflowInput, aStatus, config, unboundedLastColumn);
 | |
| 
 | |
|   // If we're not balancing, then we're already done, since we should have
 | |
|   // reflown all of our children, and there is no need for a binary search to
 | |
|   // determine proper column block-size.
 | |
|   if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) {
 | |
|     FindBestBalanceBSize(aReflowInput, aPresContext, config, colData,
 | |
|                          aDesiredSize, unboundedLastColumn, aStatus);
 | |
|   }
 | |
| 
 | |
|   if (aPresContext->HasPendingInterrupt() &&
 | |
|       aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
 | |
|     // In this situation, we might be lying about our reflow status, because
 | |
|     // our last kid (the one that got interrupted) was incomplete.  Fix that.
 | |
|     aStatus.Reset();
 | |
|   }
 | |
| 
 | |
|   NS_ASSERTION(aStatus.IsFullyComplete() ||
 | |
|                    aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
 | |
|                "Column set should be complete if the available block-size is "
 | |
|                "unconstrained");
 | |
| 
 | |
|   MOZ_ASSERT(!HasAbsolutelyPositionedChildren(),
 | |
|              "ColumnSetWrapperFrame should be the abs.pos container!");
 | |
|   FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
 | |
| 
 | |
|   COLUMN_SET_LOG("%s: End Reflow: this=%p", __func__, this);
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
 | |
|                                         const nsDisplayListSet& aLists) {
 | |
|   DisplayBorderBackgroundOutline(aBuilder, aLists);
 | |
| 
 | |
|   if (IsVisibleForPainting()) {
 | |
|     aLists.BorderBackground()->AppendNewToTop<nsDisplayColumnRule>(aBuilder,
 | |
|                                                                    this);
 | |
|   }
 | |
| 
 | |
|   // Our children won't have backgrounds so it doesn't matter where we put them.
 | |
|   for (nsIFrame* f : mFrames) {
 | |
|     BuildDisplayListForChild(aBuilder, f, aLists);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::AppendDirectlyOwnedAnonBoxes(
 | |
|     nsTArray<OwnedAnonBox>& aResult) {
 | |
|   // Everything in mFrames is continuations of the first thing in mFrames.
 | |
|   nsIFrame* column = mFrames.FirstChild();
 | |
| 
 | |
|   // We might not have any columns, apparently?
 | |
|   if (!column) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(column->Style()->GetPseudoType() == PseudoStyleType::columnContent,
 | |
|              "What sort of child is this?");
 | |
|   aResult.AppendElement(OwnedAnonBox(column));
 | |
| }
 | |
| 
 | |
| Maybe<nscoord> nsColumnSetFrame::GetNaturalBaselineBOffset(
 | |
|     WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
 | |
|   Maybe<nscoord> result;
 | |
|   for (const auto* kid : mFrames) {
 | |
|     auto kidBaseline = kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup);
 | |
|     if (!kidBaseline) {
 | |
|       continue;
 | |
|     }
 | |
|     // The kid frame may not necessarily be aligned with the columnset frame.
 | |
|     LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()),
 | |
|                         kid->GetLogicalSize(aWM)};
 | |
|     if (aBaselineGroup == BaselineSharingGroup::First) {
 | |
|       *kidBaseline += kidRect.BStart(aWM);
 | |
|     } else {
 | |
|       *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM));
 | |
|     }
 | |
|     // Take the smallest of the baselines (i.e. Closest to border-block-start
 | |
|     // for `BaselineSharingGroup::First`, border-block-end for
 | |
|     // `BaselineSharingGroup::Last`)
 | |
|     if (!result || *kidBaseline < *result) {
 | |
|       result = kidBaseline;
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| void nsColumnSetFrame::SetInitialChildList(ChildListID aListID,
 | |
|                                            nsFrameList&& aChildList) {
 | |
|   MOZ_ASSERT(aListID != FrameChildListID::Principal || aChildList.OnlyChild(),
 | |
|              "initial principal child list must have exactly one child");
 | |
|   nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::AppendFrames(ChildListID aListID,
 | |
|                                     nsFrameList&& aFrameList) {
 | |
|   MOZ_CRASH("unsupported operation");
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
 | |
|                                     const nsLineList::iterator* aPrevFrameLine,
 | |
|                                     nsFrameList&& aFrameList) {
 | |
|   MOZ_CRASH("unsupported operation");
 | |
| }
 | |
| 
 | |
| void nsColumnSetFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
 | |
|   MOZ_CRASH("unsupported operation");
 | |
| }
 | |
| #endif
 |