Source code

Revision control

Other Tools

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