bool
nsColumnSetFrame::ReflowChildren(nsHTMLReflowMetrics&     aDesiredSize,
                                 const nsHTMLReflowState& aReflowState,
                                 nsReflowStatus&          aStatus,
                                 const ReflowConfig&      aConfig,
                                 bool                     aUnboundedLastColumn,
                                 nsCollapsingMargin*      aCarriedOutBEndMargin,
                                 ColumnBalanceData&       aColData)
{
    aColData.Reset();
    bool allFit = true;
    WritingMode wm = GetWritingMode();
    bool isVertical = wm.IsVertical();
    bool isRTL = !wm.IsBidiLTR();
    bool shrinkingBSizeOnly = !NS_SUBTREE_DIRTY(this) &&
                              mLastBalanceBSize > aConfig.mColMaxBSize;

#ifdef DEBUG_roc
    printf("*** Doing column reflow pass: mLastBalanceBSize=%d, mColMaxBSize=%d, RTL=%d\n"
           "    mBalanceColCount=%d, mColISize=%d, mColGap=%d\n",
           mLastBalanceBSize, aConfig.mColMaxBSize, isRTL, aConfig.mBalanceColCount,
           aConfig.mColISize, aConfig.mColGap);
#endif

    DrainOverflowColumns();

    const bool colBSizeChanged = mLastBalanceBSize != aConfig.mColMaxBSize;

    if (colBSizeChanged) {
        mLastBalanceBSize = aConfig.mColMaxBSize;
        // 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");
    }

    // get our border and padding
    LogicalMargin borderPadding = aReflowState.ComputedLogicalBorderPadding();
    borderPadding.ApplySkipSides(GetLogicalSkipSides(&aReflowState));

    nsRect contentRect(0, 0, 0, 0);
    nsOverflowAreas overflowRects;

    nsIFrame* child = mFrames.FirstChild();
    LogicalPoint childOrigin(wm, borderPadding.IStart(wm),
                             borderPadding.BStart(wm));
    // In vertical-rl mode, columns will not be correctly placed if the
    // reflowState'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.
    nsSize containerSize = aReflowState.ComputedSizeAsContainerIfConstrained();

    // For RTL, since the columns might not fill the frame exactly, we
    // need to account for the slop. Otherwise we'll waste time moving the
    // columns by some tiny amount

    // XXX when all of layout is converted to logical coordinates, we
    //     probably won't need to do this hack any more. For now, we
    //     confine it to the legacy horizontal-rl case
    if (!isVertical && isRTL) {
        nscoord availISize = aReflowState.AvailableISize();
        if (aReflowState.ComputedISize() != NS_INTRINSICSIZE) {
            availISize = aReflowState.ComputedISize();
        }
        if (availISize != NS_INTRINSICSIZE) {
            childOrigin.I(wm) = containerSize.width - borderPadding.Left(wm) -
                                availISize;
#ifdef DEBUG_roc
            printf("*** childOrigin.iCoord = %d\n", childOrigin.I(wm));
#endif
        }
    }

    int columnCount = 0;
    int contentBEnd = 0;
    bool reflowNext = false;

    while (child) {
        // Try to skip reflowing the child. We can't skip if the child is dirty. We also can't
        // skip if the next column is dirty, because the next column's first line(s)
        // might be pullable back to this column. We can't skip if it's the last child
        // because we need to obtain the bottom margin. We can't skip
        // if this is the last column and we're supposed to assign unbounded
        // height to it, because that could change the available height 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.)
        bool skipIncremental = !aReflowState.ShouldReflowAllKids()
                               && !NS_SUBTREE_DIRTY(child)
                               && child->GetNextSibling()
                               && !(aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1)
                               && !NS_SUBTREE_DIRTY(child->GetNextSibling());
        // If we need to pull up content from the prev-in-flow then this is not just
        // a height shrink. The prev in flow will have set the dirty bit.
        // Check the overflow rect YMost instead of just the child's content height. The child
        // may have overflowing content that cares about the available height boundary.
        // (It may also have overflowing content that doesn't care about the available height
        // boundary, but if so, too bad, this optimization is defeated.)
        // We want scrollable overflow here since this is a calculation that
        // affects layout.
        bool skipResizeBSizeShrink = false;
        if (shrinkingBSizeOnly) {
            switch (wm.GetBlockDir()) {
            case WritingMode::eBlockTB:
                if (child->GetScrollableOverflowRect().YMost() <= aConfig.mColMaxBSize) {
                    skipResizeBSizeShrink = true;
                }
                break;
            case WritingMode::eBlockLR:
                if (child->GetScrollableOverflowRect().XMost() <= aConfig.mColMaxBSize) {
                    skipResizeBSizeShrink = true;
                }
                break;
            case WritingMode::eBlockRL:
                // XXX not sure how to handle this, so for now just don't attempt
                // the optimization
                break;
            default:
                NS_NOTREACHED("unknown block direction");
                break;
            }
        }

        nscoord childContentBEnd = 0;
        if (!reflowNext && (skipIncremental || skipResizeBSizeShrink)) {
            // 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 = (kidNext->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
                          ? NS_FRAME_OVERFLOW_INCOMPLETE
                          : NS_FRAME_NOT_COMPLETE;
            } else {
                aStatus = mLastFrameStatus;
            }
            childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
#ifdef DEBUG_roc
            printf("*** Skipping child #%d %p (incremental %d, resize block-size shrink %d): status = %d\n",
                   columnCount, (void*)child, skipIncremental, skipResizeBSizeShrink, aStatus);
#endif
        } else {
            LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColMaxBSize);
            if (aUnboundedLastColumn && columnCount == aConfig.mBalanceColCount - 1) {
                availSize.BSize(wm) = GetAvailableContentBSize(aReflowState);
            }

            LogicalSize computedSize = aReflowState.ComputedSize(wm);

            if (reflowNext)
                child->AddStateBits(NS_FRAME_IS_DIRTY);

            LogicalSize kidCBSize(wm, availSize.ISize(wm), computedSize.BSize(wm));
            nsHTMLReflowState kidReflowState(PresContext(), aReflowState, child,
                                             availSize, &kidCBSize);
            kidReflowState.mFlags.mIsTopOfPage = true;
            kidReflowState.mFlags.mTableIsSplittable = false;
            kidReflowState.mFlags.mIsColumnBalancing = aConfig.mBalanceColCount < INT32_MAX;

            // We need to reflow any float placeholders, even if our column height
            // hasn't changed.
            kidReflowState.mFlags.mMustReflowPlaceholders = !colBSizeChanged;

#ifdef DEBUG_roc
            printf("*** Reflowing child #%d %p: availHeight=%d\n",
                   columnCount, (void*)child,availSize.BSize(wm));
#endif

            // 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() &&
                    !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
                    !(child->GetNextSibling()->GetStateBits() & NS_FRAME_IS_DIRTY)) {
                kidReflowState.mFlags.mNextInFlowUntouched = true;
            }

            nsHTMLReflowMetrics kidDesiredSize(wm, aDesiredSize.mFlags);

            // 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.

            // Reflow the frame
            LogicalPoint origin(wm,
                                childOrigin.I(wm) +
                                kidReflowState.ComputedLogicalMargin().IStart(wm),
                                childOrigin.B(wm) +
                                kidReflowState.ComputedLogicalMargin().BStart(wm));
            ReflowChild(child, PresContext(), kidDesiredSize, kidReflowState,
                        wm, origin, containerSize, 0, aStatus);

            reflowNext = (aStatus & NS_FRAME_REFLOW_NEXTINFLOW) != 0;

#ifdef DEBUG_roc
            printf("*** Reflowed child #%d %p: status = %d, desiredSize=%d,%d CarriedOutBEndMargin=%d\n",
                   columnCount, (void*)child, aStatus, kidDesiredSize.Width(), kidDesiredSize.Height(),
                   kidDesiredSize.mCarriedOutBEndMargin.get());
#endif

            NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);

            *aCarriedOutBEndMargin = kidDesiredSize.mCarriedOutBEndMargin;

            FinishReflowChild(child, PresContext(), kidDesiredSize,
                              &kidReflowState, wm, childOrigin, containerSize, 0);

            childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
            if (childContentBEnd > aConfig.mColMaxBSize) {
                allFit = false;
            }
            if (childContentBEnd > availSize.BSize(wm)) {
                aColData.mMaxOverflowingBSize = std::max(childContentBEnd,
                                                aColData.mMaxOverflowingBSize);
            }
        }

        contentRect.UnionRect(contentRect, child->GetRect());

        ConsiderChildOverflow(overflowRects, child);
        contentBEnd = std::max(contentBEnd, childContentBEnd);
        aColData.mLastBSize = childContentBEnd;
        aColData.mSumBSize += childContentBEnd;

        // Build a continuation column if necessary
        nsIFrame* kidNextInFlow = child->GetNextInFlow();

        if (NS_FRAME_IS_FULLY_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus)) {
            NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
            child = nullptr;
            break;
        } else {
            ++columnCount;
            // 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 & NS_FRAME_REFLOW_NEXTINFLOW,
                             "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 (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
                if (!(kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
                    aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
                    reflowNext = true;
                    kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
                }
            }
            else if (kidNextInFlow->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
                aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
                reflowNext = true;
                kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
            }

            if ((contentBEnd > aReflowState.ComputedMaxBSize() ||
                    contentBEnd > aReflowState.ComputedBSize()) &&
                    aConfig.mBalanceColCount < INT32_MAX) {
                // We overflowed vertically, but have not exceeded the number of
                // columns. We're going to go into overflow columns now, so balancing
                // no longer applies.
                aColData.mHasExcessBSize = true;
            }

            if (columnCount >= aConfig.mBalanceColCount) {
                // No more columns allowed here. Stop.
                aStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
                kidNextInFlow->AddStateBits(NS_FRAME_IS_DIRTY);
                // Move any of our leftover columns to our overflow list. Our
                // next-in-flow will eventually pick them up.
                const nsFrameList& continuationColumns = mFrames.RemoveFramesAfter(child);
                if (continuationColumns.NotEmpty()) {
                    SetOverflowFrames(continuationColumns);
                }
                child = nullptr;
                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();

        if (child) {
            childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap;

#ifdef DEBUG_roc
            printf("*** NEXT CHILD ORIGIN.icoord = %d\n", childOrigin.I(wm));
#endif
        }
    }

    if (PresContext()->CheckForInterrupt(this) &&
            (GetStateBits() & 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->AddStateBits(NS_FRAME_IS_DIRTY);
        }
    }

    aColData.mMaxBSize = contentBEnd;
    LogicalSize contentSize = LogicalSize(wm, contentRect.Size());
    contentSize.BSize(wm) = std::max(contentSize.BSize(wm), contentBEnd);
    mLastFrameStatus = aStatus;

    // Apply computed and min/max values
    if (aConfig.mComputedBSize != NS_INTRINSICSIZE) {
        if (aReflowState.AvailableBSize() != NS_INTRINSICSIZE) {
            contentSize.BSize(wm) = std::min(contentSize.BSize(wm),
                                             aConfig.mComputedBSize);
        } else {
            contentSize.BSize(wm) = aConfig.mComputedBSize;
        }
    } else {
        // We add the "consumed" block-size back in so that we're applying
        // constraints to the correct bSize value, then subtract it again
        // after we've finished with the min/max calculation. This prevents us from
        // having a last continuation that is smaller than the min bSize. but which
        // has prev-in-flows, trigger a larger bSize than actually required.
        contentSize.BSize(wm) =
            aReflowState.ApplyMinMaxBSize(contentSize.BSize(wm),
                                          aConfig.mConsumedBSize);
    }
    if (aReflowState.ComputedISize() != NS_INTRINSICSIZE) {
        contentSize.ISize(wm) = aReflowState.ComputedISize();
    } else {
        contentSize.ISize(wm) =
            aReflowState.ApplyMinMaxISize(contentSize.ISize(wm));
    }

    contentSize.ISize(wm) += borderPadding.IStartEnd(wm);
    contentSize.BSize(wm) += borderPadding.BStartEnd(wm);
    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.)
    if (wm.IsVerticalRL() && containerSize.width != contentSize.Width(wm)) {
        const nsSize finalContainerSize = aDesiredSize.PhysicalSize();
        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);
        }
    }

#ifdef DEBUG_roc
    printf("*** DONE PASS feasible=%d\n", allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
           && !NS_FRAME_IS_TRUNCATED(aStatus));
#endif
    return allFit && NS_FRAME_IS_FULLY_COMPLETE(aStatus)
           && !NS_FRAME_IS_TRUNCATED(aStatus);
}