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