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