|
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 } |