layout/generic/nsBlockReflowContext.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:630b5f6746c8
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
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 /* class that a parent frame uses to reflow a block frame */
8
9 #include "nsBlockReflowContext.h"
10 #include "nsBlockReflowState.h"
11 #include "nsFloatManager.h"
12 #include "nsContainerFrame.h"
13 #include "nsBlockFrame.h"
14 #include "nsLineBox.h"
15 #include "nsLayoutUtils.h"
16
17 #ifdef DEBUG
18 #undef NOISY_MAX_ELEMENT_SIZE
19 #undef REALLY_NOISY_MAX_ELEMENT_SIZE
20 #undef NOISY_VERTICAL_MARGINS
21 #else
22 #undef NOISY_MAX_ELEMENT_SIZE
23 #undef REALLY_NOISY_MAX_ELEMENT_SIZE
24 #undef NOISY_VERTICAL_MARGINS
25 #endif
26
27 nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext,
28 const nsHTMLReflowState& aParentRS)
29 : mPresContext(aPresContext),
30 mOuterReflowState(aParentRS),
31 mMetrics(aParentRS.GetWritingMode())
32 {
33 }
34
35 static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame)
36 {
37 nsIAtom* type = aFrame->GetType();
38 if (type == nsGkAtoms::columnSetFrame)
39 return DescendIntoBlockLevelFrame(aFrame->GetFirstPrincipalChild());
40 return aFrame;
41 }
42
43 bool
44 nsBlockReflowContext::ComputeCollapsedTopMargin(const nsHTMLReflowState& aRS,
45 nsCollapsingMargin* aMargin, nsIFrame* aClearanceFrame,
46 bool* aMayNeedRetry, bool* aBlockIsEmpty)
47 {
48 // Include frame's top margin
49 aMargin->Include(aRS.ComputedPhysicalMargin().top);
50
51 // The inclusion of the bottom margin when empty is done by the caller
52 // since it doesn't need to be done by the top-level (non-recursive)
53 // caller.
54
55 #ifdef NOISY_VERTICAL_MARGINS
56 nsFrame::ListTag(stdout, aRS.frame);
57 printf(": %d => %d\n", aRS.ComputedPhysicalMargin().top, aMargin->get());
58 #endif
59
60 bool dirtiedLine = false;
61 bool setBlockIsEmpty = false;
62
63 // Calculate the frame's generational top-margin from its child
64 // blocks. Note that if the frame has a non-zero top-border or
65 // top-padding then this step is skipped because it will be a margin
66 // root. It is also skipped if the frame is a margin root for other
67 // reasons.
68 nsIFrame* frame = DescendIntoBlockLevelFrame(aRS.frame);
69 nsPresContext* prescontext = frame->PresContext();
70 nsBlockFrame* block = nullptr;
71 if (0 == aRS.ComputedPhysicalBorderPadding().top) {
72 block = nsLayoutUtils::GetAsBlock(frame);
73 if (block) {
74 bool topMarginRoot, unused;
75 block->IsMarginRoot(&topMarginRoot, &unused);
76 if (topMarginRoot) {
77 block = nullptr;
78 }
79 }
80 }
81
82 // iterate not just through the lines of 'block' but also its
83 // overflow lines and the normal and overflow lines of its next in
84 // flows. Note that this will traverse some frames more than once:
85 // for example, if A contains B and A->nextinflow contains
86 // B->nextinflow, we'll traverse B->nextinflow twice. But this is
87 // OK because our traversal is idempotent.
88 for ( ;block; block = static_cast<nsBlockFrame*>(block->GetNextInFlow())) {
89 for (int overflowLines = 0; overflowLines <= 1; ++overflowLines) {
90 nsBlockFrame::line_iterator line;
91 nsBlockFrame::line_iterator line_end;
92 bool anyLines = true;
93 if (overflowLines) {
94 nsBlockFrame::FrameLines* frames = block->GetOverflowLines();
95 nsLineList* lines = frames ? &frames->mLines : nullptr;
96 if (!lines) {
97 anyLines = false;
98 } else {
99 line = lines->begin();
100 line_end = lines->end();
101 }
102 } else {
103 line = block->begin_lines();
104 line_end = block->end_lines();
105 }
106 for (; anyLines && line != line_end; ++line) {
107 if (!aClearanceFrame && line->HasClearance()) {
108 // If we don't have a clearance frame, then we're computing
109 // the collapsed margin in the first pass, assuming that all
110 // lines have no clearance. So clear their clearance flags.
111 line->ClearHasClearance();
112 line->MarkDirty();
113 dirtiedLine = true;
114 }
115
116 bool isEmpty;
117 if (line->IsInline()) {
118 isEmpty = line->IsEmpty();
119 } else {
120 nsIFrame* kid = line->mFirstChild;
121 if (kid == aClearanceFrame) {
122 line->SetHasClearance();
123 line->MarkDirty();
124 dirtiedLine = true;
125 goto done;
126 }
127 // Here is where we recur. Now that we have determined that a
128 // generational collapse is required we need to compute the
129 // child blocks margin and so in so that we can look into
130 // it. For its margins to be computed we need to have a reflow
131 // state for it.
132
133 // We may have to construct an extra reflow state here if
134 // we drilled down through a block wrapper. At the moment
135 // we can only drill down one level so we only have to support
136 // one extra reflow state.
137 const nsHTMLReflowState* outerReflowState = &aRS;
138 if (frame != aRS.frame) {
139 NS_ASSERTION(frame->GetParent() == aRS.frame,
140 "Can only drill through one level of block wrapper");
141 nsSize availSpace(aRS.ComputedWidth(), aRS.ComputedHeight());
142 outerReflowState = new nsHTMLReflowState(prescontext,
143 aRS, frame, availSpace);
144 }
145 {
146 nsSize availSpace(outerReflowState->ComputedWidth(),
147 outerReflowState->ComputedHeight());
148 nsHTMLReflowState innerReflowState(prescontext,
149 *outerReflowState, kid,
150 availSpace);
151 // Record that we're being optimistic by assuming the kid
152 // has no clearance
153 if (kid->StyleDisplay()->mBreakType != NS_STYLE_CLEAR_NONE) {
154 *aMayNeedRetry = true;
155 }
156 if (ComputeCollapsedTopMargin(innerReflowState, aMargin, aClearanceFrame, aMayNeedRetry, &isEmpty)) {
157 line->MarkDirty();
158 dirtiedLine = true;
159 }
160 if (isEmpty)
161 aMargin->Include(innerReflowState.ComputedPhysicalMargin().bottom);
162 }
163 if (outerReflowState != &aRS) {
164 delete const_cast<nsHTMLReflowState*>(outerReflowState);
165 }
166 }
167 if (!isEmpty) {
168 if (!setBlockIsEmpty && aBlockIsEmpty) {
169 setBlockIsEmpty = true;
170 *aBlockIsEmpty = false;
171 }
172 goto done;
173 }
174 }
175 if (!setBlockIsEmpty && aBlockIsEmpty) {
176 // The first time we reach here is when this is the first block
177 // and we have processed all its normal lines.
178 setBlockIsEmpty = true;
179 // All lines are empty, or we wouldn't be here!
180 *aBlockIsEmpty = aRS.frame->IsSelfEmpty();
181 }
182 }
183 }
184 done:
185
186 if (!setBlockIsEmpty && aBlockIsEmpty) {
187 *aBlockIsEmpty = aRS.frame->IsEmpty();
188 }
189
190 #ifdef NOISY_VERTICAL_MARGINS
191 nsFrame::ListTag(stdout, aRS.frame);
192 printf(": => %d\n", aMargin->get());
193 #endif
194
195 return dirtiedLine;
196 }
197
198 nsresult
199 nsBlockReflowContext::ReflowBlock(const nsRect& aSpace,
200 bool aApplyTopMargin,
201 nsCollapsingMargin& aPrevMargin,
202 nscoord aClearance,
203 bool aIsAdjacentWithTop,
204 nsLineBox* aLine,
205 nsHTMLReflowState& aFrameRS,
206 nsReflowStatus& aFrameReflowStatus,
207 nsBlockReflowState& aState)
208 {
209 nsresult rv = NS_OK;
210 mFrame = aFrameRS.frame;
211 mSpace = aSpace;
212
213 if (!aIsAdjacentWithTop) {
214 aFrameRS.mFlags.mIsTopOfPage = false; // make sure this is cleared
215 }
216
217 if (aApplyTopMargin) {
218 mTopMargin = aPrevMargin;
219
220 #ifdef NOISY_VERTICAL_MARGINS
221 nsFrame::ListTag(stdout, mOuterReflowState.frame);
222 printf(": reflowing ");
223 nsFrame::ListTag(stdout, mFrame);
224 printf(" margin => %d, clearance => %d\n", mTopMargin.get(), aClearance);
225 #endif
226
227 // Adjust the available height if its constrained so that the
228 // child frame doesn't think it can reflow into its margin area.
229 if (NS_UNCONSTRAINEDSIZE != aFrameRS.AvailableHeight()) {
230 aFrameRS.AvailableHeight() -= mTopMargin.get() + aClearance;
231 }
232 }
233
234 nscoord tx = 0, ty = 0;
235 // The values of x and y do not matter for floats, so don't bother calculating
236 // them. Floats are guaranteed to have their own float manager, so tx and ty
237 // don't matter. mX and mY don't matter becacuse they are only used in
238 // PlaceBlock, which is not used for floats.
239 if (aLine) {
240 // Compute x/y coordinate where reflow will begin. Use the rules
241 // from 10.3.3 to determine what to apply. At this point in the
242 // reflow auto left/right margins will have a zero value.
243
244 mX = tx = mSpace.x + aFrameRS.ComputedPhysicalMargin().left;
245 mY = ty = mSpace.y + mTopMargin.get() + aClearance;
246
247 if ((mFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR) == 0)
248 aFrameRS.mBlockDelta =
249 mOuterReflowState.mBlockDelta + ty - aLine->BStart();
250 }
251
252 // Let frame know that we are reflowing it
253 mFrame->WillReflow(mPresContext);
254
255 #ifdef DEBUG
256 mMetrics.Width() = nscoord(0xdeadbeef);
257 mMetrics.Height() = nscoord(0xdeadbeef);
258 #endif
259
260 mOuterReflowState.mFloatManager->Translate(tx, ty);
261 rv = mFrame->Reflow(mPresContext, mMetrics, aFrameRS, aFrameReflowStatus);
262 mOuterReflowState.mFloatManager->Translate(-tx, -ty);
263
264 #ifdef DEBUG
265 if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
266 if (CRAZY_SIZE(mMetrics.Width()) || CRAZY_SIZE(mMetrics.Height())) {
267 printf("nsBlockReflowContext: ");
268 nsFrame::ListTag(stdout, mFrame);
269 printf(" metrics=%d,%d!\n", mMetrics.Width(), mMetrics.Height());
270 }
271 if ((mMetrics.Width() == nscoord(0xdeadbeef)) ||
272 (mMetrics.Height() == nscoord(0xdeadbeef))) {
273 printf("nsBlockReflowContext: ");
274 nsFrame::ListTag(stdout, mFrame);
275 printf(" didn't set w/h %d,%d!\n", mMetrics.Width(), mMetrics.Height());
276 }
277 }
278 #endif
279
280 if (!mFrame->HasOverflowAreas()) {
281 mMetrics.SetOverflowAreasToDesiredBounds();
282 }
283
284 if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus) ||
285 (mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
286 // If frame is complete and has a next-in-flow, we need to delete
287 // them now. Do not do this when a break-before is signaled because
288 // the frame is going to get reflowed again (and may end up wanting
289 // a next-in-flow where it ends up), unless it is an out of flow frame.
290 if (NS_FRAME_IS_FULLY_COMPLETE(aFrameReflowStatus)) {
291 nsIFrame* kidNextInFlow = mFrame->GetNextInFlow();
292 if (nullptr != kidNextInFlow) {
293 // Remove all of the childs next-in-flows. Make sure that we ask
294 // the right parent to do the removal (it's possible that the
295 // parent is not this because we are executing pullup code).
296 // Floats will eventually be removed via nsBlockFrame::RemoveFloat
297 // which detaches the placeholder from the float.
298 nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, mFrame);
299 static_cast<nsContainerFrame*>(kidNextInFlow->GetParent())
300 ->DeleteNextInFlowChild(kidNextInFlow, true);
301 }
302 }
303 }
304
305 return rv;
306 }
307
308 /**
309 * Attempt to place the block frame within the available space. If
310 * it fits, apply horizontal positioning (CSS 10.3.3), collapse
311 * margins (CSS2 8.3.1). Also apply relative positioning.
312 */
313 bool
314 nsBlockReflowContext::PlaceBlock(const nsHTMLReflowState& aReflowState,
315 bool aForceFit,
316 nsLineBox* aLine,
317 nsCollapsingMargin& aBottomMarginResult,
318 nsOverflowAreas& aOverflowAreas,
319 nsReflowStatus aReflowStatus,
320 nscoord aContainerWidth)
321 {
322 // Compute collapsed bottom margin value.
323 if (NS_FRAME_IS_COMPLETE(aReflowStatus)) {
324 aBottomMarginResult = mMetrics.mCarriedOutBottomMargin;
325 aBottomMarginResult.Include(aReflowState.ComputedPhysicalMargin().bottom);
326 } else {
327 // The used bottom-margin is set to zero above a break.
328 aBottomMarginResult.Zero();
329 }
330
331 nsPoint position(mX, mY);
332 nscoord backupContainingBlockAdvance = 0;
333
334 // Check whether the block's bottom margin collapses with its top
335 // margin. See CSS 2.1 section 8.3.1; those rules seem to match
336 // nsBlockFrame::IsEmpty(). Any such block must have zero height so
337 // check that first. Note that a block can have clearance and still
338 // have adjoining top/bottom margins, because the clearance goes
339 // above the top margin.
340 // Mark the frame as non-dirty; it has been reflowed (or we wouldn't
341 // be here), and we don't want to assert in CachedIsEmpty()
342 mFrame->RemoveStateBits(NS_FRAME_IS_DIRTY);
343 bool empty = 0 == mMetrics.Height() && aLine->CachedIsEmpty();
344 if (empty) {
345 // Collapse the bottom margin with the top margin that was already
346 // applied.
347 aBottomMarginResult.Include(mTopMargin);
348
349 #ifdef NOISY_VERTICAL_MARGINS
350 printf(" ");
351 nsFrame::ListTag(stdout, mOuterReflowState.frame);
352 printf(": ");
353 nsFrame::ListTag(stdout, mFrame);
354 printf(" -- collapsing top & bottom margin together; y=%d spaceY=%d\n",
355 position.y, mSpace.y);
356 #endif
357 // Section 8.3.1 of CSS 2.1 says that blocks with adjoining
358 // top/bottom margins whose top margin collapses with their
359 // parent's top margin should have their top border-edge at the
360 // top border-edge of their parent. We actually don't have to do
361 // anything special to make this happen. In that situation,
362 // nsBlockFrame::ShouldApplyTopMargin will have returned false,
363 // and mTopMargin and aClearance will have been zero in
364 // ReflowBlock.
365
366 // If we did apply our top margin, but now we're collapsing it
367 // into the bottom margin, we need to back up the containing
368 // block's y-advance by our top margin so that it doesn't get
369 // counted twice. Note that here we're allowing the line's bounds
370 // to become different from the block's position; we do this
371 // because the containing block will place the next line at the
372 // line's YMost, and it must place the next line at a different
373 // point from where this empty block will be.
374 backupContainingBlockAdvance = mTopMargin.get();
375 }
376
377 // See if the frame fit. If it's the first frame or empty then it
378 // always fits. If the height is unconstrained then it always fits,
379 // even if there's some sort of integer overflow that makes y +
380 // mMetrics.Height() appear to go beyond the available height.
381 if (!empty && !aForceFit && mSpace.height != NS_UNCONSTRAINEDSIZE) {
382 nscoord yMost = position.y - backupContainingBlockAdvance + mMetrics.Height();
383 if (yMost > mSpace.YMost()) {
384 // didn't fit, we must acquit.
385 mFrame->DidReflow(mPresContext, &aReflowState, nsDidReflowStatus::FINISHED);
386 return false;
387 }
388 }
389
390 aLine->SetBounds(aReflowState.GetWritingMode(),
391 nsRect(position.x,
392 position.y - backupContainingBlockAdvance,
393 mMetrics.Width(),
394 mMetrics.Height()),
395 aContainerWidth);
396
397 aReflowState.ApplyRelativePositioning(&position);
398
399 // Now place the frame and complete the reflow process
400 nsContainerFrame::FinishReflowChild(mFrame, mPresContext, mMetrics,
401 &aReflowState, position.x, position.y, 0);
402
403 aOverflowAreas = mMetrics.mOverflowAreas + position;
404
405 return true;
406 }

mercurial