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