tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsColumnSetFrame.cpp (57409B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* rendering object for css3 multi-column layout */
      8 
      9 #include "nsColumnSetFrame.h"
     10 
     11 #include "mozilla/ColumnUtils.h"
     12 #include "mozilla/Logging.h"
     13 #include "mozilla/PresShell.h"
     14 #include "mozilla/StaticPrefs_layout.h"
     15 #include "mozilla/ToString.h"
     16 #include "nsCSSRendering.h"
     17 #include "nsDisplayList.h"
     18 #include "nsIFrameInlines.h"
     19 #include "nsLayoutUtils.h"
     20 
     21 using namespace mozilla;
     22 using namespace mozilla::layout;
     23 
     24 // To see this log, use $ MOZ_LOG=ColumnSet:4 ./mach run
     25 static LazyLogModule sColumnSetLog("ColumnSet");
     26 #define COLUMN_SET_LOG(msg, ...) \
     27  MOZ_LOG(sColumnSetLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
     28 
     29 class nsDisplayColumnRule final : public nsPaintedDisplayItem {
     30 public:
     31  nsDisplayColumnRule(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
     32      : nsPaintedDisplayItem(aBuilder, aFrame) {
     33    MOZ_COUNT_CTOR(nsDisplayColumnRule);
     34  }
     35 
     36  MOZ_COUNTED_DTOR_FINAL(nsDisplayColumnRule)
     37 
     38  nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override {
     39    *aSnap = false;
     40    // We just return the frame's ink-overflow rect, which is guaranteed to
     41    // contain all the column-rule areas.  It's not worth calculating the exact
     42    // union of those areas since it would only lead to performance improvements
     43    // during painting in rare edge cases.
     44    return mFrame->InkOverflowRect() + ToReferenceFrame();
     45  }
     46 
     47  bool CreateWebRenderCommands(
     48      mozilla::wr::DisplayListBuilder& aBuilder,
     49      mozilla::wr::IpcResourceUpdateQueue& aResources,
     50      const StackingContextHelper& aSc,
     51      mozilla::layers::RenderRootStateManager* aManager,
     52      nsDisplayListBuilder* aDisplayListBuilder) override;
     53  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
     54 
     55  NS_DISPLAY_DECL_NAME("ColumnRule", TYPE_COLUMN_RULE);
     56 
     57 private:
     58  nsTArray<nsCSSBorderRenderer> mBorderRenderers;
     59 };
     60 
     61 void nsDisplayColumnRule::Paint(nsDisplayListBuilder* aBuilder,
     62                                gfxContext* aCtx) {
     63  static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
     64      mBorderRenderers, aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame());
     65 
     66  for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end();
     67       iter++) {
     68    iter->DrawBorders();
     69  }
     70 }
     71 
     72 bool nsDisplayColumnRule::CreateWebRenderCommands(
     73    mozilla::wr::DisplayListBuilder& aBuilder,
     74    mozilla::wr::IpcResourceUpdateQueue& aResources,
     75    const StackingContextHelper& aSc,
     76    mozilla::layers::RenderRootStateManager* aManager,
     77    nsDisplayListBuilder* aDisplayListBuilder) {
     78  RefPtr dt = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
     79  if (!dt || !dt->IsValid()) {
     80    return false;
     81  }
     82  gfxContext screenRefCtx(dt);
     83 
     84  bool dummy;
     85  static_cast<nsColumnSetFrame*>(mFrame)->CreateBorderRenderers(
     86      mBorderRenderers, &screenRefCtx, GetBounds(aDisplayListBuilder, &dummy),
     87      ToReferenceFrame());
     88 
     89  if (mBorderRenderers.IsEmpty()) {
     90    return true;
     91  }
     92 
     93  for (auto& renderer : mBorderRenderers) {
     94    renderer.CreateWebRenderCommands(this, aBuilder, aResources, aSc);
     95  }
     96 
     97  return true;
     98 }
     99 
    100 // The maximum number of columns we support.
    101 static constexpr int32_t kMaxColumnCount = 1000;
    102 
    103 /**
    104 * Tracking issues:
    105 *
    106 * XXX cursor movement around the top and bottom of colums seems to make the
    107 * editor lose the caret.
    108 *
    109 * XXX should we support CSS columns applied to table elements?
    110 */
    111 nsContainerFrame* NS_NewColumnSetFrame(PresShell* aPresShell,
    112                                       ComputedStyle* aStyle,
    113                                       nsFrameState aStateFlags) {
    114  nsColumnSetFrame* it =
    115      new (aPresShell) nsColumnSetFrame(aStyle, aPresShell->GetPresContext());
    116  it->AddStateBits(aStateFlags);
    117  return it;
    118 }
    119 
    120 NS_IMPL_FRAMEARENA_HELPERS(nsColumnSetFrame)
    121 
    122 nsColumnSetFrame::nsColumnSetFrame(ComputedStyle* aStyle,
    123                                   nsPresContext* aPresContext)
    124    : nsContainerFrame(aStyle, aPresContext, kClassID),
    125      mLastBalanceBSize(NS_UNCONSTRAINEDSIZE) {}
    126 
    127 void nsColumnSetFrame::ForEachColumnRule(
    128    const std::function<void(const nsRect& lineRect)>& aSetLineRect,
    129    const nsPoint& aPt) const {
    130  nsIFrame* child = mFrames.FirstChild();
    131  if (!child) {
    132    return;  // no columns
    133  }
    134 
    135  nsIFrame* nextSibling = child->GetNextSibling();
    136  if (!nextSibling) {
    137    return;  // 1 column only - this means no gap to draw on
    138  }
    139 
    140  const nsStyleColumn* colStyle = StyleColumn();
    141  nscoord ruleWidth = colStyle->GetColumnRuleWidth();
    142  if (!ruleWidth) {
    143    return;
    144  }
    145 
    146  WritingMode wm = GetWritingMode();
    147  bool isVertical = wm.IsVertical();
    148  bool isRTL = wm.IsBidiRTL();
    149 
    150  nsRect contentRect = GetContentRectRelativeToSelf() + aPt;
    151  nsSize ruleSize = isVertical ? nsSize(contentRect.width, ruleWidth)
    152                               : nsSize(ruleWidth, contentRect.height);
    153 
    154  while (nextSibling) {
    155    // The frame tree goes RTL in RTL.
    156    // The |prevFrame| and |nextFrame| frames here are the visually preceding
    157    // (left/above) and following (right/below) frames, not in logical writing-
    158    // mode direction.
    159    nsIFrame* prevFrame = isRTL ? nextSibling : child;
    160    nsIFrame* nextFrame = isRTL ? child : nextSibling;
    161 
    162    // Each child frame's position coordinates is actually relative to this
    163    // nsColumnSetFrame.
    164    // linePt will be at the top-left edge to paint the line.
    165    nsPoint linePt;
    166    if (isVertical) {
    167      nscoord edgeOfPrev = prevFrame->GetRect().YMost() + aPt.y;
    168      nscoord edgeOfNext = nextFrame->GetRect().Y() + aPt.y;
    169      linePt = nsPoint(contentRect.x,
    170                       (edgeOfPrev + edgeOfNext - ruleSize.height) / 2);
    171    } else {
    172      nscoord edgeOfPrev = prevFrame->GetRect().XMost() + aPt.x;
    173      nscoord edgeOfNext = nextFrame->GetRect().X() + aPt.x;
    174      linePt = nsPoint((edgeOfPrev + edgeOfNext - ruleSize.width) / 2,
    175                       contentRect.y);
    176    }
    177 
    178    aSetLineRect(nsRect(linePt, ruleSize));
    179 
    180    child = nextSibling;
    181    nextSibling = nextSibling->GetNextSibling();
    182  }
    183 }
    184 
    185 void nsColumnSetFrame::CreateBorderRenderers(
    186    nsTArray<nsCSSBorderRenderer>& aBorderRenderers, gfxContext* aCtx,
    187    const nsRect& aDirtyRect, const nsPoint& aPt) {
    188  WritingMode wm = GetWritingMode();
    189  bool isVertical = wm.IsVertical();
    190  const nsStyleColumn* colStyle = StyleColumn();
    191  StyleBorderStyle ruleStyle;
    192 
    193  // Per spec, inset => ridge and outset => groove
    194  if (colStyle->mColumnRuleStyle == StyleBorderStyle::Inset) {
    195    ruleStyle = StyleBorderStyle::Ridge;
    196  } else if (colStyle->mColumnRuleStyle == StyleBorderStyle::Outset) {
    197    ruleStyle = StyleBorderStyle::Groove;
    198  } else {
    199    ruleStyle = colStyle->mColumnRuleStyle;
    200  }
    201 
    202  nscoord ruleWidth = colStyle->GetColumnRuleWidth();
    203  if (!ruleWidth) {
    204    return;
    205  }
    206 
    207  aBorderRenderers.Clear();
    208  nscolor ruleColor =
    209      GetVisitedDependentColor(&nsStyleColumn::mColumnRuleColor);
    210 
    211  nsPresContext* pc = PresContext();
    212  // In order to re-use a large amount of code, we treat the column rule as a
    213  // border. We create a new border style object and fill in all the details of
    214  // the column rule as the left border. PaintBorder() does all the rendering
    215  // for us, so we not only save an enormous amount of code but we'll support
    216  // all the line styles that we support on borders!
    217  nsStyleBorder border;
    218  Sides skipSides;
    219  if (isVertical) {
    220    border.SetBorderWidth(eSideTop, ruleWidth, pc->AppUnitsPerDevPixel());
    221    border.SetBorderStyle(eSideTop, ruleStyle);
    222    border.mBorderTopColor = StyleColor::FromColor(ruleColor);
    223    skipSides |= mozilla::SideBits::eLeftRight;
    224    skipSides |= mozilla::SideBits::eBottom;
    225  } else {
    226    border.SetBorderWidth(eSideLeft, ruleWidth, pc->AppUnitsPerDevPixel());
    227    border.SetBorderStyle(eSideLeft, ruleStyle);
    228    border.mBorderLeftColor = StyleColor::FromColor(ruleColor);
    229    skipSides |= mozilla::SideBits::eTopBottom;
    230    skipSides |= mozilla::SideBits::eRight;
    231  }
    232  // If we use box-decoration-break: slice (the default), the border
    233  // renderers will require clipping if we have continuations (see the
    234  // aNeedsClip parameter to ConstructBorderRenderer in nsCSSRendering).
    235  //
    236  // Since it doesn't matter which box-decoration-break we use since
    237  // we're only drawing borders (and not border-images), use 'clone'.
    238  border.mBoxDecorationBreak = StyleBoxDecorationBreak::Clone;
    239 
    240  ForEachColumnRule(
    241      [&](const nsRect& aLineRect) {
    242        // Assert that we're not drawing a border-image here; if we were, we
    243        // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
    244        // returns.
    245        MOZ_ASSERT(border.mBorderImageSource.IsNone());
    246 
    247        gfx::DrawTarget* dt = aCtx ? aCtx->GetDrawTarget() : nullptr;
    248        bool borderIsEmpty = false;
    249        Maybe<nsCSSBorderRenderer> br =
    250            nsCSSRendering::CreateBorderRendererWithStyleBorder(
    251                pc, dt, this, aDirtyRect, aLineRect, border, Style(),
    252                &borderIsEmpty, skipSides);
    253        if (br.isSome()) {
    254          MOZ_ASSERT(!borderIsEmpty);
    255          aBorderRenderers.AppendElement(br.value());
    256        }
    257      },
    258      aPt);
    259 }
    260 
    261 static uint32_t ColumnBalancingDepth(const ReflowInput& aReflowInput,
    262                                     uint32_t aMaxDepth) {
    263  uint32_t depth = 0;
    264  for (const ReflowInput* ri = aReflowInput.mParentReflowInput;
    265       ri && depth < aMaxDepth; ri = ri->mParentReflowInput) {
    266    if (ri->mFlags.mIsColumnBalancing) {
    267      ++depth;
    268    }
    269  }
    270  return depth;
    271 }
    272 
    273 nsColumnSetFrame::ReflowConfig nsColumnSetFrame::ChooseColumnStrategy(
    274    const ReflowInput& aReflowInput, bool aForceAuto = false) const {
    275  const nsStyleColumn* colStyle = StyleColumn();
    276  nscoord availContentISize = aReflowInput.AvailableISize();
    277  if (aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE) {
    278    availContentISize = aReflowInput.ComputedISize();
    279  }
    280 
    281  nscoord colBSize = aReflowInput.AvailableBSize();
    282  nscoord colGap =
    283      ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize());
    284  int32_t numColumns =
    285      colStyle->mColumnCount.IsAuto()
    286          ? 0
    287          : std::min(colStyle->mColumnCount.AsInteger(), kMaxColumnCount);
    288 
    289  // If column-fill is set to 'balance' or we have a column-span sibling, then
    290  // we want to balance the columns.
    291  bool isBalancing = (colStyle->mColumnFill == StyleColumnFill::Balance ||
    292                      HasColumnSpanSiblings()) &&
    293                     !aForceAuto;
    294  if (isBalancing) {
    295    const uint32_t kMaxNestedColumnBalancingDepth = 2;
    296    const uint32_t balancingDepth =
    297        ColumnBalancingDepth(aReflowInput, kMaxNestedColumnBalancingDepth);
    298    if (balancingDepth == kMaxNestedColumnBalancingDepth) {
    299      isBalancing = false;
    300      numColumns = 1;
    301      aForceAuto = true;
    302    }
    303  }
    304 
    305  nscoord colISize;
    306  // In vertical writing-mode, "column-width" (inline size) will actually be
    307  // physical height, but its CSS name is still column-width.
    308  if (colStyle->mColumnWidth.IsLength()) {
    309    colISize =
    310        ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
    311    NS_ASSERTION(colISize >= 0, "negative column width");
    312    // Reduce column count if necessary to make columns fit in the
    313    // available width. Compute max number of columns that fit in
    314    // availContentISize, satisfying colGap*(maxColumns - 1) +
    315    // colISize*maxColumns <= availContentISize
    316    if (availContentISize != NS_UNCONSTRAINEDSIZE && colGap + colISize > 0 &&
    317        numColumns > 0) {
    318      // This expression uses truncated rounding, which is what we
    319      // want
    320      int32_t maxColumns =
    321          std::min(nscoord(kMaxColumnCount),
    322                   (availContentISize + colGap) / (colGap + colISize));
    323      numColumns = std::max(1, std::min(numColumns, maxColumns));
    324    }
    325  } else if (numColumns > 0 && availContentISize != NS_UNCONSTRAINEDSIZE) {
    326    nscoord iSizeMinusGaps = availContentISize - colGap * (numColumns - 1);
    327    colISize = iSizeMinusGaps / numColumns;
    328  } else {
    329    colISize = NS_UNCONSTRAINEDSIZE;
    330  }
    331  // Take care of the situation where there's only one column but it's
    332  // still too wide
    333  colISize = std::max(1, std::min(colISize, availContentISize));
    334 
    335  nscoord expectedISizeLeftOver = 0;
    336 
    337  if (colISize != NS_UNCONSTRAINEDSIZE &&
    338      availContentISize != NS_UNCONSTRAINEDSIZE) {
    339    // distribute leftover space
    340 
    341    // First, determine how many columns will be showing if the column
    342    // count is auto
    343    if (numColumns <= 0) {
    344      // choose so that colGap*(nominalColumnCount - 1) +
    345      // colISize*nominalColumnCount is nearly availContentISize
    346      // make sure to round down
    347      if (colGap + colISize > 0) {
    348        numColumns = (availContentISize + colGap) / (colGap + colISize);
    349        // The number of columns should never exceed kMaxColumnCount.
    350        numColumns = std::min(kMaxColumnCount, numColumns);
    351      }
    352      if (numColumns <= 0) {
    353        numColumns = 1;
    354      }
    355    }
    356 
    357    // Compute extra space and divide it among the columns
    358    nscoord extraSpace =
    359        std::max(0, availContentISize -
    360                        (colISize * numColumns + colGap * (numColumns - 1)));
    361    nscoord extraToColumns = extraSpace / numColumns;
    362    colISize += extraToColumns;
    363    expectedISizeLeftOver = extraSpace - (extraToColumns * numColumns);
    364  }
    365 
    366  if (isBalancing) {
    367    if (numColumns <= 0) {
    368      // Hmm, auto column count, column width or available width is unknown,
    369      // and balancing is required. Let's just use one column then.
    370      numColumns = 1;
    371    }
    372    colBSize = std::min(mLastBalanceBSize, colBSize);
    373  } else {
    374    // CSS Fragmentation spec says, "To guarantee progress, fragmentainers are
    375    // assumed to have a minimum block size of 1px regardless of their used
    376    // size." https://drafts.csswg.org/css-break/#breaking-rules
    377    //
    378    // Note: we don't enforce the minimum block-size during balancing because
    379    // this affects the result. If a balancing column container or its
    380    // next-in-flows has zero block-size, it eventually gives up balancing, and
    381    // ends up here.
    382    colBSize = std::max(colBSize, nsPresContext::CSSPixelsToAppUnits(1));
    383  }
    384 
    385  ReflowConfig config;
    386  config.mUsedColCount = numColumns;
    387  config.mColISize = colISize;
    388  config.mExpectedISizeLeftOver = expectedISizeLeftOver;
    389  config.mColGap = colGap;
    390  config.mColBSize = colBSize;
    391  config.mIsBalancing = isBalancing;
    392  config.mForceAuto = aForceAuto;
    393  config.mKnownFeasibleBSize = NS_UNCONSTRAINEDSIZE;
    394  config.mKnownInfeasibleBSize = 0;
    395 
    396  COLUMN_SET_LOG(
    397      "%s: this=%p, mUsedColCount=%d, mColISize=%d, "
    398      "mExpectedISizeLeftOver=%d, mColGap=%d, mColBSize=%d, mIsBalancing=%d",
    399      __func__, this, config.mUsedColCount, config.mColISize,
    400      config.mExpectedISizeLeftOver, config.mColGap, config.mColBSize,
    401      config.mIsBalancing);
    402 
    403  return config;
    404 }
    405 
    406 static void MarkPrincipalChildrenDirty(nsIFrame* aFrame) {
    407  for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
    408    childFrame->MarkSubtreeDirty();
    409  }
    410 }
    411 
    412 static void MoveChildTo(nsIFrame* aChild, LogicalPoint aOrigin, WritingMode aWM,
    413                        const nsSize& aContainerSize) {
    414  if (aChild->GetLogicalPosition(aWM, aContainerSize) == aOrigin) {
    415    return;
    416  }
    417 
    418  aChild->SetPosition(aWM, aOrigin, aContainerSize);
    419 }
    420 
    421 nscoord nsColumnSetFrame::IntrinsicISize(const IntrinsicSizeInput& input,
    422                                         IntrinsicISizeType aType) {
    423  return aType == IntrinsicISizeType::MinISize ? MinISize(input)
    424                                               : PrefISize(input);
    425 }
    426 
    427 nscoord nsColumnSetFrame::MinISize(const IntrinsicSizeInput& aInput) {
    428  nscoord iSize = 0;
    429 
    430  if (mFrames.FirstChild()) {
    431    iSize = mFrames.FirstChild()->GetMinISize(aInput);
    432  }
    433  const nsStyleColumn* colStyle = StyleColumn();
    434  if (colStyle->mColumnWidth.IsLength()) {
    435    nscoord colISize =
    436        ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
    437    // As available width reduces to zero, we reduce our number of columns
    438    // to one, and don't enforce the column width, so just return the min
    439    // of the child's min-width with any specified column width.
    440    iSize = std::min(iSize, colISize);
    441  } else {
    442    NS_ASSERTION(!colStyle->mColumnCount.IsAuto(),
    443                 "column-count and column-width can't both be auto");
    444    // As available width reduces to zero, we still have mColumnCount columns,
    445    // so compute our minimum size based on the number of columns and their gaps
    446    // and minimum per-column size.
    447    nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
    448    iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount.AsInteger(),
    449                                        colGap, iSize);
    450  }
    451  // XXX count forced column breaks here? Maybe we should return the child's
    452  // min-width times the minimum number of columns.
    453  return iSize;
    454 }
    455 
    456 nscoord nsColumnSetFrame::PrefISize(const IntrinsicSizeInput& aInput) {
    457  // Our preferred width is our desired column width, if specified, otherwise
    458  // the child's preferred width, times the number of columns, plus the width
    459  // of any required column gaps
    460  // XXX what about forced column breaks here?
    461  const nsStyleColumn* colStyle = StyleColumn();
    462 
    463  nscoord colISize;
    464  if (colStyle->mColumnWidth.IsLength()) {
    465    colISize =
    466        ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength());
    467  } else if (mFrames.FirstChild()) {
    468    colISize = mFrames.FirstChild()->GetPrefISize(aInput);
    469  } else {
    470    colISize = 0;
    471  }
    472 
    473  // If column-count is auto, assume one column.
    474  uint32_t numColumns =
    475      colStyle->mColumnCount.IsAuto() ? 1 : colStyle->mColumnCount.AsInteger();
    476  nscoord colGap = ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE);
    477  return ColumnUtils::IntrinsicISize(numColumns, colGap, colISize);
    478 }
    479 
    480 nsColumnSetFrame::ColumnBalanceData nsColumnSetFrame::ReflowColumns(
    481    ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput,
    482    nsReflowStatus& aStatus, const ReflowConfig& aConfig,
    483    bool aUnboundedLastColumn) {
    484  ColumnBalanceData colData;
    485  bool allFit = true;
    486  WritingMode wm = GetWritingMode();
    487  const bool isRTL = wm.IsBidiRTL();
    488  const bool shrinkingBSize = mLastBalanceBSize > aConfig.mColBSize;
    489  const bool changingBSize = mLastBalanceBSize != aConfig.mColBSize;
    490 
    491  COLUMN_SET_LOG(
    492      "%s: Doing column reflow pass: mLastBalanceBSize=%d,"
    493      " mColBSize=%d, RTL=%d, mUsedColCount=%d,"
    494      " mColISize=%d, mColGap=%d",
    495      __func__, mLastBalanceBSize, aConfig.mColBSize, isRTL,
    496      aConfig.mUsedColCount, aConfig.mColISize, aConfig.mColGap);
    497 
    498  DrainOverflowColumns();
    499 
    500  if (changingBSize) {
    501    mLastBalanceBSize = aConfig.mColBSize;
    502    // XXX Seems like this could fire if incremental reflow pushed the column
    503    // set down so we reflow incrementally with a different available height.
    504    // We need a way to do an incremental reflow and be sure availableHeight
    505    // changes are taken account of! Right now I think block frames with
    506    // absolute children might exit early.
    507    /*
    508    NS_ASSERTION(
    509        aKidReason != eReflowReason_Incremental,
    510        "incremental reflow should not have changed the balance height");
    511    */
    512  }
    513 
    514  nsRect contentRect(0, 0, 0, 0);
    515  OverflowAreas overflowRects;
    516 
    517  nsIFrame* child = mFrames.FirstChild();
    518  LogicalPoint childOrigin(wm, 0, 0);
    519 
    520  // In vertical-rl mode, columns will not be correctly placed if the
    521  // reflowInput's ComputedWidth() is UNCONSTRAINED (in which case we'll get
    522  // a containerSize.width of zero here). In that case, the column positions
    523  // will be adjusted later, after our correct contentSize is known.
    524  //
    525  // When column-span is enabled, containerSize.width is always constrained.
    526  // However, for RTL, we need to adjust the column positions as well after our
    527  // correct containerSize is known.
    528  nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
    529 
    530  const nscoord computedBSize =
    531      aReflowInput.mParentReflowInput->ComputedBSize();
    532  nscoord contentBEnd = 0;
    533  bool reflowNext = false;
    534 
    535  while (child) {
    536    const bool reflowLastColumnWithUnconstrainedAvailBSize =
    537        aUnboundedLastColumn && colData.mColCount == aConfig.mUsedColCount &&
    538        aConfig.mIsBalancing;
    539 
    540    // We need to reflow the child (column) ...
    541    bool reflowChild =
    542        // if we are told to do so;
    543        aReflowInput.ShouldReflowAllKids() ||
    544        // if the child is dirty;
    545        child->IsSubtreeDirty() ||
    546        // if it's the last child because we need to obtain the block-end
    547        // margin;
    548        !child->GetNextSibling() ||
    549        // if the next column is dirty, because the next column's first line(s)
    550        // might be pullable back to this column;
    551        child->GetNextSibling()->IsSubtreeDirty() ||
    552        // if this is the last column and we are supposed to assign unbounded
    553        // block-size to it, because that could change the available block-size
    554        // from the last time we reflowed it and we should try to pull all the
    555        // content from its next sibling (Note that it might be the last column,
    556        // but not be the last child because the desired number of columns has
    557        // changed.)
    558        reflowLastColumnWithUnconstrainedAvailBSize;
    559 
    560    // If column-fill is auto (not the default), then we might need to
    561    // move content between columns for any change in column block-size.
    562    //
    563    // The same is true if we have a non-'auto' computed block-size.
    564    //
    565    // FIXME: It's not clear to me why it's *ever* valid to have
    566    // reflowChild be false when changingBSize is true, since it
    567    // seems like a child broken over multiple columns might need to
    568    // change the size of the fragment in each column.
    569    if (!reflowChild && changingBSize &&
    570        (StyleColumn()->mColumnFill == StyleColumnFill::Auto ||
    571         computedBSize != NS_UNCONSTRAINEDSIZE)) {
    572      reflowChild = true;
    573    }
    574    // If we need to pull up content from the prev-in-flow then this is not just
    575    // a block-size shrink. The prev in flow will have set the dirty bit.
    576    // Check the overflow rect YMost instead of just the child's content
    577    // block-size. The child may have overflowing content that cares about the
    578    // available block-size boundary. (It may also have overflowing content that
    579    // doesn't care about the available block-size boundary, but if so, too bad,
    580    // this optimization is defeated.) We want scrollable overflow here since
    581    // this is a calculation that affects layout.
    582    if (!reflowChild && shrinkingBSize) {
    583      switch (wm.GetBlockDir()) {
    584        case WritingMode::BlockDir::TB:
    585          if (child->ScrollableOverflowRect().YMost() > aConfig.mColBSize) {
    586            reflowChild = true;
    587          }
    588          break;
    589        case WritingMode::BlockDir::LR:
    590          if (child->ScrollableOverflowRect().XMost() > aConfig.mColBSize) {
    591            reflowChild = true;
    592          }
    593          break;
    594        case WritingMode::BlockDir::RL:
    595          // XXX not sure how to handle this, so for now just don't attempt
    596          // the optimization
    597          reflowChild = true;
    598          break;
    599        default:
    600          MOZ_ASSERT_UNREACHABLE("unknown block direction");
    601          break;
    602      }
    603    }
    604 
    605    nscoord childContentBEnd = 0;
    606    if (!reflowNext && !reflowChild) {
    607      // This child does not need to be reflowed, but we may need to move it
    608      MoveChildTo(child, childOrigin, wm, containerSize);
    609 
    610      // If this is the last frame then make sure we get the right status
    611      nsIFrame* kidNext = child->GetNextSibling();
    612      if (kidNext) {
    613        aStatus.Reset();
    614        if (kidNext->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
    615          aStatus.SetOverflowIncomplete();
    616        } else {
    617          aStatus.SetIncomplete();
    618        }
    619      } else {
    620        aStatus = mLastFrameStatus;
    621      }
    622      childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
    623 
    624      COLUMN_SET_LOG("%s: Skipping child #%d %p: status=%s", __func__,
    625                     colData.mColCount, child, ToString(aStatus).c_str());
    626    } else {
    627      LogicalSize availSize(wm, aConfig.mColISize, aConfig.mColBSize);
    628      if (reflowLastColumnWithUnconstrainedAvailBSize) {
    629        availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
    630 
    631        COLUMN_SET_LOG(
    632            "%s: Reflowing last column with unconstrained block-size. Change "
    633            "available block-size from %d to %d",
    634            __func__, aConfig.mColBSize, availSize.BSize(wm));
    635      }
    636 
    637      if (reflowNext) {
    638        child->MarkSubtreeDirty();
    639      }
    640 
    641      LogicalSize kidCBSize(wm, availSize.ISize(wm), computedBSize);
    642      ReflowInput kidReflowInput(PresContext(), aReflowInput, child, availSize,
    643                                 Some(kidCBSize));
    644      kidReflowInput.mFlags.mIsTopOfPage = [&]() {
    645        const bool isNestedMulticolOrInRootPaginatedDoc =
    646            aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits(
    647                NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
    648            PresContext()->IsRootPaginatedDocument();
    649        if (isNestedMulticolOrInRootPaginatedDoc) {
    650          if (aConfig.mForceAuto) {
    651            // If we are forced to fill columns sequentially, force fit the
    652            // content whether we are at top of page or not.
    653            return true;
    654          }
    655          if (aReflowInput.mFlags.mIsTopOfPage) {
    656            // If this is the last balancing reflow, we want to force fit
    657            // content to avoid infinite loops.
    658            return !aConfig.mIsBalancing || aConfig.mIsLastBalancingReflow;
    659          }
    660          // If we are a not at the top of page, we shouldn't force fit content.
    661          // This is because our ColumnSetWrapperFrame can be pushed to the next
    662          // column or page and reflowed again with a potentially larger
    663          // available block-size.
    664          return false;
    665        }
    666        // We are a top-level multicol in a non-paginated root document or in a
    667        // subdocument (regardless of whether the root document is paginated).
    668        // Force fit the content only if we are not balancing columns.
    669        return !aConfig.mIsBalancing;
    670      }();
    671      kidReflowInput.mFlags.mTableIsSplittable = false;
    672      kidReflowInput.mFlags.mIsColumnBalancing = aConfig.mIsBalancing;
    673      kidReflowInput.mFlags.mIsInLastColumnBalancingReflow =
    674          aConfig.mIsLastBalancingReflow;
    675      kidReflowInput.mBreakType = ReflowInput::BreakType::Column;
    676 
    677      // We need to reflow any float placeholders, even if our column block-size
    678      // hasn't changed.
    679      kidReflowInput.mFlags.mMustReflowPlaceholders = !changingBSize;
    680 
    681      COLUMN_SET_LOG(
    682          "%s: Reflowing child #%d %p: availSize=(%d,%d), kidCBSize=(%d,%d), "
    683          "child's mIsTopOfPage=%d",
    684          __func__, colData.mColCount, child, availSize.ISize(wm),
    685          availSize.BSize(wm), kidCBSize.ISize(wm), kidCBSize.BSize(wm),
    686          kidReflowInput.mFlags.mIsTopOfPage);
    687 
    688      // Note if the column's next in flow is not being changed by this
    689      // incremental reflow. This may allow the current column to avoid trying
    690      // to pull lines from the next column.
    691      if (child->GetNextSibling() && !HasAnyStateBits(NS_FRAME_IS_DIRTY) &&
    692          !child->GetNextSibling()->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
    693        kidReflowInput.mFlags.mNextInFlowUntouched = true;
    694      }
    695 
    696      ReflowOutput kidDesiredSize(wm);
    697 
    698      // XXX it would be cool to consult the float manager for the
    699      // previous block to figure out the region of floats from the
    700      // previous column that extend into this column, and subtract
    701      // that region from the new float manager.  So you could stick a
    702      // really big float in the first column and text in following
    703      // columns would flow around it.
    704 
    705      MOZ_ASSERT(kidReflowInput.ComputedLogicalMargin(wm).IsAllZero(),
    706                 "-moz-column-content has no margin!");
    707      aStatus.Reset();
    708      ReflowChild(child, PresContext(), kidDesiredSize, kidReflowInput, wm,
    709                  childOrigin, containerSize, ReflowChildFlags::Default,
    710                  aStatus);
    711 
    712      if (colData.mColCount == 1 && aStatus.IsInlineBreakBefore()) {
    713        COLUMN_SET_LOG("%s: Content in the first column reports break-before!",
    714                       __func__);
    715        allFit = false;
    716        break;
    717      }
    718 
    719      reflowNext = aStatus.NextInFlowNeedsReflow();
    720 
    721      // The carried-out block-end margin of column content might be non-zero
    722      // when we try to find the best column balancing block size, but it should
    723      // never affect the size column set nor be further carried out. Set it to
    724      // zero.
    725      //
    726      // FIXME: For some types of fragmentation, we should carry the margin into
    727      // the next column. Also see
    728      // https://drafts.csswg.org/css-break-4/#break-margins
    729      //
    730      // FIXME: This should never happen for the last column, since it should be
    731      // a margin root; see nsBlockFrame::IsMarginRoot(). However, sometimes the
    732      // last column has an empty continuation while searching for the best
    733      // column balancing bsize, which prevents the last column from being a
    734      // margin root.
    735      kidDesiredSize.mCarriedOutBEndMargin.Zero();
    736 
    737      NS_FRAME_TRACE_REFLOW_OUT("Column::Reflow", aStatus);
    738 
    739      FinishReflowChild(child, PresContext(), kidDesiredSize, &kidReflowInput,
    740                        wm, childOrigin, containerSize,
    741                        ReflowChildFlags::Default);
    742 
    743      childContentBEnd = nsLayoutUtils::CalculateContentBEnd(wm, child);
    744      if (childContentBEnd > aConfig.mColBSize) {
    745        allFit = false;
    746      }
    747      if (childContentBEnd > availSize.BSize(wm)) {
    748        colData.mMaxOverflowingBSize =
    749            std::max(childContentBEnd, colData.mMaxOverflowingBSize);
    750      }
    751 
    752      COLUMN_SET_LOG(
    753          "%s: Reflowed child #%d %p: status=%s, desiredSize=(%d,%d), "
    754          "childContentBEnd=%d, CarriedOutBEndMargin=%d (ignored)",
    755          __func__, colData.mColCount, child, ToString(aStatus).c_str(),
    756          kidDesiredSize.ISize(wm), kidDesiredSize.BSize(wm), childContentBEnd,
    757          kidDesiredSize.mCarriedOutBEndMargin.Get());
    758    }
    759 
    760    contentRect.UnionRect(contentRect, child->GetRect());
    761 
    762    ConsiderChildOverflow(overflowRects, child);
    763    contentBEnd = std::max(contentBEnd, childContentBEnd);
    764    colData.mLastBSize = childContentBEnd;
    765    colData.mSumBSize += childContentBEnd;
    766 
    767    // Build a continuation column if necessary
    768    nsIFrame* kidNextInFlow = child->GetNextInFlow();
    769 
    770    if (aStatus.IsFullyComplete()) {
    771      NS_ASSERTION(!kidNextInFlow, "next in flow should have been deleted");
    772      child = nullptr;
    773      break;
    774    }
    775 
    776    // Make sure that the column has a next-in-flow. If not, we must
    777    // create one to hold the overflowing stuff, even if we're just
    778    // going to put it on our overflow list and let *our*
    779    // next in flow handle it.
    780    if (!kidNextInFlow) {
    781      NS_ASSERTION(aStatus.NextInFlowNeedsReflow(),
    782                   "We have to create a continuation, but the block doesn't "
    783                   "want us to reflow it?");
    784 
    785      // We need to create a continuing column
    786      kidNextInFlow = CreateNextInFlow(child);
    787    }
    788 
    789    // Make sure we reflow a next-in-flow when it switches between being
    790    // normal or overflow container
    791    if (aStatus.IsOverflowIncomplete()) {
    792      if (!kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
    793        aStatus.SetNextInFlowNeedsReflow();
    794        reflowNext = true;
    795        kidNextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
    796      }
    797    } else if (kidNextInFlow->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
    798      aStatus.SetNextInFlowNeedsReflow();
    799      reflowNext = true;
    800      kidNextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
    801    }
    802 
    803    // We have reached the maximum number of columns. If we are balancing, stop
    804    // this reflow and continue finding the optimal balancing block-size.
    805    //
    806    // Otherwise, i.e. we are not balancing, stop this reflow and let the parent
    807    // of our multicol container create a next-in-flow if all of the following
    808    // conditions are met.
    809    //
    810    // 1) We fill columns sequentially by the request of the style, not by our
    811    // internal needs, i.e. aConfig.mForceAuto is false.
    812    //
    813    // We don't want to stop this reflow when we force fill the columns
    814    // sequentially. We usually go into this mode when giving up balancing, and
    815    // this is the last resort to fit all our children by creating overflow
    816    // columns.
    817    //
    818    // 2) In a fragmented context, our multicol container still has block-size
    819    // left for its next-in-flow, i.e.
    820    // aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft is false.
    821    //
    822    // Note that in a continuous context, i.e. our multicol container's
    823    // available block-size is unconstrained, if it has a fixed block-size
    824    // mColumnSetWrapperHasNoBSizeLeft is always true because nothing stops it
    825    // from applying all its block-size in the first-in-flow. Otherwise, i.e.
    826    // our multicol container has an unconstrained block-size, we shouldn't be
    827    // here because all our children should fit in the very first column even if
    828    // mColumnSetWrapperHasNoBSizeLeft is false.
    829    //
    830    // According to the definition of mColumnSetWrapperHasNoBSizeLeft, if the
    831    // bit is *not* set, either our multicol container has unconstrained
    832    // block-size, or it has a constrained block-size and has block-size left
    833    // for its next-in-flow. In either cases, the parent of our multicol
    834    // container can create a next-in-flow for the container that guaranteed to
    835    // have non-zero block-size for the container's children.
    836    //
    837    // Put simply, if either one of the above conditions is not met, we are
    838    // going to create more overflow columns until all our children are fit.
    839    if (colData.mColCount >= aConfig.mUsedColCount &&
    840        (aConfig.mIsBalancing ||
    841         (!aConfig.mForceAuto &&
    842          !aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft))) {
    843      NS_ASSERTION(aConfig.mIsBalancing ||
    844                       aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
    845                   "Why are we here if we have unlimited block-size to fill "
    846                   "columns sequentially.");
    847 
    848      // No more columns allowed here. Stop.
    849      aStatus.SetNextInFlowNeedsReflow();
    850      kidNextInFlow->MarkSubtreeDirty();
    851      // Move any of our leftover columns to our overflow list. Our
    852      // next-in-flow will eventually pick them up.
    853      nsFrameList continuationColumns = mFrames.TakeFramesAfter(child);
    854      if (continuationColumns.NotEmpty()) {
    855        SetOverflowFrames(std::move(continuationColumns));
    856      }
    857      child = nullptr;
    858 
    859      COLUMN_SET_LOG("%s: We are not going to create overflow columns.",
    860                     __func__);
    861      break;
    862    }
    863 
    864    if (PresContext()->HasPendingInterrupt()) {
    865      // Stop the loop now while |child| still points to the frame that bailed
    866      // out.  We could keep going here and condition a bunch of the code in
    867      // this loop on whether there's an interrupt, or even just keep going and
    868      // trying to reflow the blocks (even though we know they'll interrupt
    869      // right after their first line), but stopping now is conceptually the
    870      // simplest (and probably fastest) thing.
    871      break;
    872    }
    873 
    874    // Advance to the next column
    875    child = child->GetNextSibling();
    876    ++colData.mColCount;
    877 
    878    if (child) {
    879      childOrigin.I(wm) += aConfig.mColISize + aConfig.mColGap;
    880 
    881      COLUMN_SET_LOG("%s: Next childOrigin.iCoord=%d", __func__,
    882                     childOrigin.I(wm));
    883    }
    884  }
    885 
    886  if (PresContext()->CheckForInterrupt(this) &&
    887      HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
    888    // Mark all our kids starting with |child| dirty
    889 
    890    // Note that this is a CheckForInterrupt call, not a HasPendingInterrupt,
    891    // because we might have interrupted while reflowing |child|, and since
    892    // we're about to add a dirty bit to |child| we need to make sure that
    893    // |this| is scheduled to have dirty bits marked on it and its ancestors.
    894    // Otherwise, when we go to mark dirty bits on |child|'s ancestors we'll
    895    // bail out immediately, since it'll already have a dirty bit.
    896    for (; child; child = child->GetNextSibling()) {
    897      child->MarkSubtreeDirty();
    898    }
    899  }
    900 
    901  colData.mMaxBSize = contentBEnd;
    902  LogicalSize contentSize = LogicalSize(wm, contentRect.Size());
    903  contentSize.BSize(wm) = std::max(contentSize.BSize(wm), contentBEnd);
    904  mLastFrameStatus = aStatus;
    905 
    906  if (computedBSize != NS_UNCONSTRAINEDSIZE && !HasColumnSpanSiblings()) {
    907    NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
    908                 "Available block-size should be constrained because it's "
    909                 "restricted by the computed block-size when our reflow "
    910                 "input is created in nsBlockFrame::ReflowBlockFrame()!");
    911 
    912    // If a) our parent ColumnSetWrapper has constrained block-size
    913    // (nsBlockFrame::ReflowBlockFrame() applies the block-size constraint
    914    // when creating a ReflowInput for ColumnSetFrame child); and b) we are the
    915    // sole ColumnSet or the last ColumnSet continuation split by column-spans
    916    // in a ColumnSetWrapper, extend our block-size to consume the available
    917    // block-size so that the column-rules are drawn to the content block-end
    918    // edge of the multicol container.
    919    contentSize.BSize(wm) =
    920        std::max(contentSize.BSize(wm), aReflowInput.AvailableBSize());
    921  }
    922 
    923  aDesiredSize.SetSize(wm, contentSize);
    924  aDesiredSize.mOverflowAreas = overflowRects;
    925  aDesiredSize.UnionOverflowAreasWithDesiredBounds();
    926 
    927  // In vertical-rl mode, make a second pass if necessary to reposition the
    928  // columns with the correct container width. (In other writing modes,
    929  // correct containerSize was not required for column positioning so we don't
    930  // need this fixup.)
    931  //
    932  // RTL column positions also depend on ColumnSet's actual contentSize. We need
    933  // this fixup, too.
    934  if ((wm.IsVerticalRL() || isRTL) &&
    935      containerSize.width != contentSize.Width(wm)) {
    936    const nsSize finalContainerSize = aDesiredSize.PhysicalSize();
    937    OverflowAreas overflowRects;
    938    for (nsIFrame* child : mFrames) {
    939      // Get the logical position as set previously using a provisional or
    940      // dummy containerSize, and reset with the correct container size.
    941      child->SetPosition(wm, child->GetLogicalPosition(wm, containerSize),
    942                         finalContainerSize);
    943      ConsiderChildOverflow(overflowRects, child);
    944    }
    945    aDesiredSize.mOverflowAreas = overflowRects;
    946    aDesiredSize.UnionOverflowAreasWithDesiredBounds();
    947  }
    948 
    949  colData.mFeasible = allFit && aStatus.IsFullyComplete();
    950 
    951  COLUMN_SET_LOG(
    952      "%s: Done column reflow pass: %s, mMaxBSize=%d, mSumBSize=%d, "
    953      "mLastBSize=%d, mMaxOverflowingBSize=%d",
    954      __func__, colData.mFeasible ? "Feasible :)" : "Infeasible :(",
    955      colData.mMaxBSize, colData.mSumBSize, colData.mLastBSize,
    956      colData.mMaxOverflowingBSize);
    957 
    958  return colData;
    959 }
    960 
    961 void nsColumnSetFrame::DrainOverflowColumns() {
    962  // First grab the prev-in-flows overflows and reparent them to this
    963  // frame.
    964  nsPresContext* presContext = PresContext();
    965  nsColumnSetFrame* prev = static_cast<nsColumnSetFrame*>(GetPrevInFlow());
    966  if (prev) {
    967    AutoFrameListPtr overflows(presContext, prev->StealOverflowFrames());
    968    if (overflows) {
    969      mFrames.InsertFrames(this, nullptr, std::move(*overflows));
    970    }
    971  }
    972 
    973  // Now pull back our own overflows and append them to our children.
    974  // We don't need to reparent them since we're already their parent.
    975  AutoFrameListPtr overflows(presContext, StealOverflowFrames());
    976  if (overflows) {
    977    // We're already the parent for these frames, so no need to set
    978    // their parent again.
    979    mFrames.AppendFrames(nullptr, std::move(*overflows));
    980  }
    981 }
    982 
    983 void nsColumnSetFrame::FindBestBalanceBSize(const ReflowInput& aReflowInput,
    984                                            nsPresContext* aPresContext,
    985                                            ReflowConfig& aConfig,
    986                                            ColumnBalanceData aColData,
    987                                            ReflowOutput& aDesiredSize,
    988                                            bool aUnboundedLastColumn,
    989                                            nsReflowStatus& aStatus) {
    990  MOZ_ASSERT(aConfig.mIsBalancing,
    991             "Why are we here if we are not balancing columns?");
    992 
    993  const nscoord availableContentBSize = aReflowInput.AvailableBSize();
    994 
    995  // Termination of the algorithm below is guaranteed because
    996  // aConfig.knownFeasibleBSize - aConfig.knownInfeasibleBSize decreases in
    997  // every iteration.
    998  int32_t iterationCount = 1;
    999 
   1000  // We set this flag when we detect that we may contain a frame
   1001  // that can break anywhere (thus foiling the linear decrease-by-one
   1002  // search)
   1003  bool maybeContinuousBreakingDetected = false;
   1004  bool possibleOptimalBSizeDetected = false;
   1005 
   1006  // This is the extra block-size added to the optimal column block-size
   1007  // estimation which is calculated in the while-loop by dividing
   1008  // aColData.mSumBSize into N columns.
   1009  //
   1010  // The constant is arbitrary. We use a half of line-height first. In case a
   1011  // column container uses *zero* (or a very small) line-height, use a half of
   1012  // default line-height 1140/2 = 570 app units as the minimum value. Otherwise
   1013  // we might take more than necessary iterations before finding a feasible
   1014  // block-size.
   1015  nscoord extraBlockSize = std::max(570, aReflowInput.GetLineHeight() / 2);
   1016 
   1017  // We use divide-by-N to estimate the optimal column block-size only if the
   1018  // last column's available block-size is unbounded.
   1019  bool foundFeasibleBSizeCloserToBest = !aUnboundedLastColumn;
   1020 
   1021  // Stop the binary search when the difference of the feasible and infeasible
   1022  // block-size is within this gap. Here we use one device pixel.
   1023  const int32_t gapToStop = aPresContext->DevPixelsToAppUnits(1);
   1024 
   1025  while (!aPresContext->HasPendingInterrupt()) {
   1026    nscoord lastKnownFeasibleBSize = aConfig.mKnownFeasibleBSize;
   1027 
   1028    // Record what we learned from the last reflow
   1029    if (aColData.mFeasible) {
   1030      // mMaxBSize is feasible. Also, mLastBalanceBSize is feasible.
   1031      aConfig.mKnownFeasibleBSize =
   1032          std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
   1033      aConfig.mKnownFeasibleBSize =
   1034          std::min(aConfig.mKnownFeasibleBSize, mLastBalanceBSize);
   1035 
   1036      // Furthermore, no block-size less than the block-size of the last
   1037      // column can ever be feasible. (We might be able to reduce the
   1038      // block-size of a non-last column by moving content to a later column,
   1039      // but we can't do that with the last column.)
   1040      if (aColData.mColCount == aConfig.mUsedColCount) {
   1041        aConfig.mKnownInfeasibleBSize =
   1042            std::max(aConfig.mKnownInfeasibleBSize, aColData.mLastBSize - 1);
   1043      }
   1044    } else {
   1045      aConfig.mKnownInfeasibleBSize =
   1046          std::max(aConfig.mKnownInfeasibleBSize, mLastBalanceBSize);
   1047 
   1048      // If a column didn't fit in its available block-size, then its current
   1049      // block-size must be the minimum block-size for unbreakable content in
   1050      // the column, and therefore no smaller block-size can be feasible.
   1051      aConfig.mKnownInfeasibleBSize = std::max(
   1052          aConfig.mKnownInfeasibleBSize, aColData.mMaxOverflowingBSize - 1);
   1053 
   1054      if (aUnboundedLastColumn) {
   1055        // The last column is unbounded, so all content got reflowed, so the
   1056        // mMaxBSize is feasible.
   1057        aConfig.mKnownFeasibleBSize =
   1058            std::min(aConfig.mKnownFeasibleBSize, aColData.mMaxBSize);
   1059 
   1060        NS_ASSERTION(mLastFrameStatus.IsComplete(),
   1061                     "Last column should be complete if the available "
   1062                     "block-size is unconstrained!");
   1063      }
   1064    }
   1065 
   1066    COLUMN_SET_LOG(
   1067        "%s: this=%p, mKnownInfeasibleBSize=%d, mKnownFeasibleBSize=%d",
   1068        __func__, this, aConfig.mKnownInfeasibleBSize,
   1069        aConfig.mKnownFeasibleBSize);
   1070 
   1071    if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) {
   1072      // aConfig.mKnownFeasibleBSize is where we want to be. This can happen in
   1073      // the very first iteration when a column container solely has a tall
   1074      // unbreakable child that overflows the container.
   1075      break;
   1076    }
   1077 
   1078    if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
   1079      // There's no feasible block-size to fit our contents. We may need to
   1080      // reflow one more time after this loop.
   1081      break;
   1082    }
   1083 
   1084    const nscoord gap =
   1085        aConfig.mKnownFeasibleBSize - aConfig.mKnownInfeasibleBSize;
   1086    if (gap <= gapToStop && possibleOptimalBSizeDetected) {
   1087      // We detected a possible optimal block-size in the last iteration. If it
   1088      // is infeasible, we may need to reflow one more time after this loop.
   1089      break;
   1090    }
   1091 
   1092    if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) {
   1093      // We decreased the feasible block-size by one twip only. This could
   1094      // indicate that there is a continuously breakable child frame
   1095      // that we are crawling through.
   1096      maybeContinuousBreakingDetected = true;
   1097    }
   1098 
   1099    nscoord nextGuess = aConfig.mKnownInfeasibleBSize + gap / 2;
   1100    if (aConfig.mKnownFeasibleBSize - nextGuess < extraBlockSize &&
   1101        !maybeContinuousBreakingDetected) {
   1102      // We're close to our target, so just try shrinking just the
   1103      // minimum amount that will cause one of our columns to break
   1104      // differently.
   1105      nextGuess = aConfig.mKnownFeasibleBSize - 1;
   1106    } else if (!foundFeasibleBSizeCloserToBest) {
   1107      // Make a guess by dividing mSumBSize into N columns and adding
   1108      // extraBlockSize to try to make it on the feasible side.
   1109      nextGuess = aColData.mSumBSize / aConfig.mUsedColCount + extraBlockSize;
   1110      // Sanitize it
   1111      nextGuess = std::clamp(nextGuess, aConfig.mKnownInfeasibleBSize + 1,
   1112                             aConfig.mKnownFeasibleBSize - 1);
   1113      // We keep doubling extraBlockSize in every iteration until we find a
   1114      // feasible guess.
   1115      extraBlockSize *= 2;
   1116    } else if (aConfig.mKnownFeasibleBSize == NS_UNCONSTRAINEDSIZE) {
   1117      // This can happen when we had a next-in-flow so we didn't
   1118      // want to do an unbounded block-size measuring step. Let's just increase
   1119      // from the infeasible block-size by some reasonable amount.
   1120      nextGuess = aConfig.mKnownInfeasibleBSize * 2 + extraBlockSize;
   1121    } else if (gap <= gapToStop) {
   1122      // Floor nextGuess to the greatest multiple of gapToStop below or equal to
   1123      // mKnownFeasibleBSize.
   1124      nextGuess = aConfig.mKnownFeasibleBSize / gapToStop * gapToStop;
   1125      possibleOptimalBSizeDetected = true;
   1126    }
   1127 
   1128    // Don't bother guessing more than our block-size constraint.
   1129    nextGuess = std::min(availableContentBSize, nextGuess);
   1130 
   1131    COLUMN_SET_LOG("%s: Choosing next guess=%d, iteration=%d", __func__,
   1132                   nextGuess, iterationCount);
   1133    ++iterationCount;
   1134 
   1135    aConfig.mColBSize = nextGuess;
   1136 
   1137    aUnboundedLastColumn = false;
   1138    MarkPrincipalChildrenDirty(this);
   1139    aColData =
   1140        ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig, false);
   1141 
   1142    if (!foundFeasibleBSizeCloserToBest && aColData.mFeasible) {
   1143      foundFeasibleBSizeCloserToBest = true;
   1144    }
   1145  }
   1146 
   1147  if (!aColData.mFeasible && !aPresContext->HasPendingInterrupt()) {
   1148    // We need to reflow one more time at the feasible block-size to
   1149    // get a valid layout.
   1150    if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
   1151      aConfig.mColBSize = availableContentBSize;
   1152      if (mLastBalanceBSize == availableContentBSize) {
   1153        // If we end up here, we have a constrained available content
   1154        // block-size, and our last column's block-size exceeds it. Also, if
   1155        // this is the first balancing iteration, the last column is given
   1156        // unconstrained available block-size, so it has a fully complete
   1157        // reflow status. Therefore, we always want to reflow again at the
   1158        // available content block-size to get a valid layout and a correct
   1159        // reflow status (likely an *incomplete* status) so that our column
   1160        // container can be fragmented if needed.
   1161 
   1162        if (aReflowInput.mFlags.mColumnSetWrapperHasNoBSizeLeft) {
   1163          // If our column container has a constrained block-size (either in a
   1164          // paginated context or in a nested column container), and is going
   1165          // to consume all its computed block-size in this fragment, then our
   1166          // column container has no block-size left to contain our
   1167          // next-in-flows. We have to give up balancing, and create our
   1168          // own overflow columns.
   1169          //
   1170          // We don't want to create overflow columns immediately when our
   1171          // content doesn't fit since this changes our reflow status from
   1172          // incomplete to complete. Valid reasons include 1) the outer column
   1173          // container might do column balancing, and it can enlarge the
   1174          // available content block-size so that the nested one could fit its
   1175          // content in next balancing iteration; or 2) the outer column
   1176          // container is filling columns sequentially, and may have more
   1177          // inline-size to create more column boxes for the nested column
   1178          // container's next-in-flows.
   1179          aConfig = ChooseColumnStrategy(aReflowInput, true);
   1180        }
   1181      }
   1182    } else {
   1183      aConfig.mColBSize = aConfig.mKnownFeasibleBSize;
   1184    }
   1185 
   1186    // This is our last attempt to reflow. If our column container's available
   1187    // block-size is unconstrained, make sure that the last column is
   1188    // allowed to have arbitrary block-size here, even though we were
   1189    // balancing. Otherwise we'd have to split, and it's not clear what we'd
   1190    // do with that.
   1191    COLUMN_SET_LOG("%s: Last attempt to call ReflowColumns", __func__);
   1192    aConfig.mIsLastBalancingReflow = true;
   1193    const bool forceUnboundedLastColumn =
   1194        aReflowInput.mParentReflowInput->AvailableBSize() ==
   1195        NS_UNCONSTRAINEDSIZE;
   1196    MarkPrincipalChildrenDirty(this);
   1197    ReflowColumns(aDesiredSize, aReflowInput, aStatus, aConfig,
   1198                  forceUnboundedLastColumn);
   1199  }
   1200 }
   1201 
   1202 void nsColumnSetFrame::Reflow(nsPresContext* aPresContext,
   1203                              ReflowOutput& aDesiredSize,
   1204                              const ReflowInput& aReflowInput,
   1205                              nsReflowStatus& aStatus) {
   1206  MarkInReflow();
   1207  // Don't support interruption in columns
   1208  nsPresContext::InterruptPreventer noInterrupts(aPresContext);
   1209 
   1210  DO_GLOBAL_REFLOW_COUNT("nsColumnSetFrame");
   1211  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
   1212 
   1213  MOZ_ASSERT(aReflowInput.mCBReflowInput->mFrame->StyleColumn()
   1214                 ->IsColumnContainerStyle(),
   1215             "The column container should have relevant column styles!");
   1216  MOZ_ASSERT(aReflowInput.mParentReflowInput->mFrame->IsColumnSetWrapperFrame(),
   1217             "The column container should be ColumnSetWrapperFrame!");
   1218  MOZ_ASSERT(
   1219      aReflowInput.ComputedLogicalBorderPadding(aReflowInput.GetWritingMode())
   1220          .IsAllZero(),
   1221      "Only the column container can have border and padding!");
   1222  MOZ_ASSERT(
   1223      GetChildList(FrameChildListID::OverflowContainers).IsEmpty() &&
   1224          GetChildList(FrameChildListID::ExcessOverflowContainers).IsEmpty(),
   1225      "ColumnSetFrame should store overflow containers in principal "
   1226      "child list!");
   1227 
   1228  //------------ Handle Incremental Reflow -----------------
   1229 
   1230  COLUMN_SET_LOG("%s: Begin Reflow: this=%p, is nested multicol=%d", __func__,
   1231                 this,
   1232                 aReflowInput.mParentReflowInput->mFrame->HasAnyStateBits(
   1233                     NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR));
   1234 
   1235  // If inline size is unconstrained, set aForceAuto to true to allow
   1236  // the columns to expand in the inline direction. (This typically
   1237  // happens in orthogonal flows where the inline direction is the
   1238  // container's block direction).
   1239  ReflowConfig config = ChooseColumnStrategy(
   1240      aReflowInput, aReflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE);
   1241 
   1242  // If balancing, then we allow the last column to grow to unbounded
   1243  // block-size during the first reflow. This gives us a way to estimate
   1244  // what the average column block-size should be, because we can measure
   1245  // the block-size of all the columns and sum them up. But don't do this
   1246  // if we have a next in flow because we don't want to suck all its
   1247  // content back here and then have to push it out again!
   1248  nsIFrame* nextInFlow = GetNextInFlow();
   1249  bool unboundedLastColumn = config.mIsBalancing && !nextInFlow;
   1250  const ColumnBalanceData colData = ReflowColumns(
   1251      aDesiredSize, aReflowInput, aStatus, config, unboundedLastColumn);
   1252 
   1253  // If we're not balancing, then we're already done, since we should have
   1254  // reflown all of our children, and there is no need for a binary search to
   1255  // determine proper column block-size.
   1256  if (config.mIsBalancing && !aPresContext->HasPendingInterrupt()) {
   1257    FindBestBalanceBSize(aReflowInput, aPresContext, config, colData,
   1258                         aDesiredSize, unboundedLastColumn, aStatus);
   1259  }
   1260 
   1261  if (aPresContext->HasPendingInterrupt() &&
   1262      aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
   1263    // In this situation, we might be lying about our reflow status, because
   1264    // our last kid (the one that got interrupted) was incomplete.  Fix that.
   1265    aStatus.Reset();
   1266  }
   1267 
   1268  NS_ASSERTION(aStatus.IsFullyComplete() ||
   1269                   aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
   1270               "Column set should be complete if the available block-size is "
   1271               "unconstrained");
   1272 
   1273  MOZ_ASSERT(!HasAbsolutelyPositionedChildren(),
   1274             "ColumnSetWrapperFrame should be the abs.pos container!");
   1275  FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
   1276 
   1277  COLUMN_SET_LOG("%s: End Reflow: this=%p", __func__, this);
   1278 }
   1279 
   1280 void nsColumnSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
   1281                                        const nsDisplayListSet& aLists) {
   1282  DisplayBorderBackgroundOutline(aBuilder, aLists);
   1283 
   1284  if (IsVisibleForPainting()) {
   1285    aLists.BorderBackground()->AppendNewToTop<nsDisplayColumnRule>(aBuilder,
   1286                                                                   this);
   1287  }
   1288 
   1289  if (HidesContent()) {
   1290    return;
   1291  }
   1292 
   1293  // Our children won't have backgrounds so it doesn't matter where we put them.
   1294  for (nsIFrame* f : mFrames) {
   1295    BuildDisplayListForChild(aBuilder, f, aLists);
   1296  }
   1297 }
   1298 
   1299 void nsColumnSetFrame::AppendDirectlyOwnedAnonBoxes(
   1300    nsTArray<OwnedAnonBox>& aResult) {
   1301  // Everything in mFrames is continuations of the first thing in mFrames.
   1302  nsIFrame* column = mFrames.FirstChild();
   1303 
   1304  // We might not have any columns, apparently?
   1305  if (!column) {
   1306    return;
   1307  }
   1308 
   1309  MOZ_ASSERT(column->Style()->GetPseudoType() == PseudoStyleType::columnContent,
   1310             "What sort of child is this?");
   1311  aResult.AppendElement(OwnedAnonBox(column));
   1312 }
   1313 
   1314 Maybe<nscoord> nsColumnSetFrame::GetNaturalBaselineBOffset(
   1315    WritingMode aWM, BaselineSharingGroup aBaselineGroup,
   1316    BaselineExportContext aExportContext) const {
   1317  Maybe<nscoord> result;
   1318  for (const auto* kid : mFrames) {
   1319    auto kidBaseline =
   1320        kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext);
   1321    if (!kidBaseline) {
   1322      continue;
   1323    }
   1324    // The kid frame may not necessarily be aligned with the columnset frame.
   1325    LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()),
   1326                        kid->GetLogicalSize(aWM)};
   1327    if (aBaselineGroup == BaselineSharingGroup::First) {
   1328      *kidBaseline += kidRect.BStart(aWM);
   1329    } else {
   1330      *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM));
   1331    }
   1332    // Take the smallest of the baselines (i.e. Closest to border-block-start
   1333    // for `BaselineSharingGroup::First`, border-block-end for
   1334    // `BaselineSharingGroup::Last`)
   1335    if (!result || *kidBaseline < *result) {
   1336      result = kidBaseline;
   1337    }
   1338  }
   1339  return result;
   1340 }
   1341 
   1342 #ifdef DEBUG
   1343 void nsColumnSetFrame::SetInitialChildList(ChildListID aListID,
   1344                                           nsFrameList&& aChildList) {
   1345  MOZ_ASSERT(aListID != FrameChildListID::Principal || aChildList.OnlyChild(),
   1346             "initial principal child list must have exactly one child");
   1347  nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList));
   1348 }
   1349 
   1350 void nsColumnSetFrame::AppendFrames(ChildListID aListID,
   1351                                    nsFrameList&& aFrameList) {
   1352  MOZ_CRASH("unsupported operation");
   1353 }
   1354 
   1355 void nsColumnSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
   1356                                    const nsLineList::iterator* aPrevFrameLine,
   1357                                    nsFrameList&& aFrameList) {
   1358  MOZ_CRASH("unsupported operation");
   1359 }
   1360 
   1361 void nsColumnSetFrame::RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) {
   1362  MOZ_CRASH("unsupported operation");
   1363 }
   1364 #endif