/* -*- 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 "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 mBorderRenderers; }; void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { static_cast(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 screenRefCtx = gfxContext::CreateOrNull( gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); bool dummy; static_cast(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& 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& 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 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 = !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)", __func__, colData.mColCount, child, availSize.ISize(wm), availSize.BSize(wm), kidCBSize.ISize(wm), kidCBSize.BSize(wm)); // 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(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. 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 ----------------- // 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); } void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { DisplayBorderBackgroundOutline(aBuilder, aLists); if (IsVisibleForPainting()) { aLists.BorderBackground()->AppendNewToTop(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& 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)); } #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