Coverage Report

Created: 2018-09-25 14:53

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