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 : * Steve Clark <buster@netscape.com>
25 : * Robert O'Callahan <roc+moz@cs.cmu.edu>
26 : * L. David Baron <dbaron@dbaron.org>
27 : * Mats Palmgren <mats.palmgren@bredband.net>
28 : *
29 : * Alternatively, the contents of this file may be used under the terms of
30 : * either of the GNU General Public License Version 2 or later (the "GPL"),
31 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 : * in which case the provisions of the GPL or the LGPL are applicable instead
33 : * of those above. If you wish to allow use of your version of this file only
34 : * under the terms of either the GPL or the LGPL, and not to allow others to
35 : * use your version of this file under the terms of the MPL, indicate your
36 : * decision by deleting the provisions above and replace them with the notice
37 : * and other provisions required by the GPL or the LGPL. If you do not delete
38 : * the provisions above, a recipient may use your version of this file under
39 : * the terms of any one of the MPL, the GPL or the LGPL.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : /* state used in reflow of block frames */
44 :
45 : #include "nsBlockReflowContext.h"
46 : #include "nsBlockReflowState.h"
47 : #include "nsBlockFrame.h"
48 : #include "nsLineLayout.h"
49 : #include "nsPresContext.h"
50 : #include "nsGkAtoms.h"
51 : #include "nsIFrame.h"
52 : #include "nsFrameManager.h"
53 : #include "mozilla/AutoRestore.h"
54 : #include "FrameLayerBuilder.h"
55 :
56 : #include "nsINameSpaceManager.h"
57 :
58 : #include "mozilla/Util.h" // for DebugOnly
59 :
60 : #ifdef DEBUG
61 : #include "nsBlockDebugFlags.h"
62 : #endif
63 :
64 : using namespace mozilla;
65 : using namespace mozilla::layout;
66 :
67 0 : nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState,
68 : nsPresContext* aPresContext,
69 : nsBlockFrame* aFrame,
70 : const nsHTMLReflowMetrics& aMetrics,
71 : bool aTopMarginRoot,
72 : bool aBottomMarginRoot,
73 : bool aBlockNeedsFloatManager)
74 : : mBlock(aFrame),
75 : mPresContext(aPresContext),
76 : mReflowState(aReflowState),
77 : mPushedFloats(nsnull),
78 : mOverflowTracker(nsnull),
79 : mPrevBottomMargin(),
80 : mLineNumber(0),
81 : mFlags(0),
82 0 : mFloatBreakType(NS_STYLE_CLEAR_NONE)
83 : {
84 0 : SetFlag(BRS_ISFIRSTINFLOW, aFrame->GetPrevInFlow() == nsnull);
85 : SetFlag(BRS_ISOVERFLOWCONTAINER,
86 0 : IS_TRUE_OVERFLOW_CONTAINER(aFrame));
87 :
88 0 : const nsMargin& borderPadding = BorderPadding();
89 :
90 0 : if (aTopMarginRoot || 0 != aReflowState.mComputedBorderPadding.top) {
91 0 : SetFlag(BRS_ISTOPMARGINROOT, true);
92 : }
93 0 : if (aBottomMarginRoot || 0 != aReflowState.mComputedBorderPadding.bottom) {
94 0 : SetFlag(BRS_ISBOTTOMMARGINROOT, true);
95 : }
96 0 : if (GetFlag(BRS_ISTOPMARGINROOT)) {
97 0 : SetFlag(BRS_APPLYTOPMARGIN, true);
98 : }
99 0 : if (aBlockNeedsFloatManager) {
100 0 : SetFlag(BRS_FLOAT_MGR, true);
101 : }
102 :
103 0 : mFloatManager = aReflowState.mFloatManager;
104 :
105 0 : NS_ASSERTION(mFloatManager,
106 : "FloatManager should be set in nsBlockReflowState" );
107 0 : if (mFloatManager) {
108 : // Save the coordinate system origin for later.
109 0 : mFloatManager->GetTranslation(mFloatManagerX, mFloatManagerY);
110 0 : mFloatManager->PushState(&mFloatManagerStateBefore); // never popped
111 : }
112 :
113 0 : mReflowStatus = NS_FRAME_COMPLETE;
114 :
115 0 : mPresContext = aPresContext;
116 0 : mNextInFlow = static_cast<nsBlockFrame*>(mBlock->GetNextInFlow());
117 :
118 0 : NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowState.ComputedWidth(),
119 : "have unconstrained width; this should only result from "
120 : "very large sizes, not attempts at intrinsic width "
121 : "calculation");
122 0 : mContentArea.width = aReflowState.ComputedWidth();
123 :
124 : // Compute content area height. Unlike the width, if we have a
125 : // specified style height we ignore it since extra content is
126 : // managed by the "overflow" property. When we don't have a
127 : // specified style height then we may end up limiting our height if
128 : // the availableHeight is constrained (this situation occurs when we
129 : // are paginated).
130 0 : if (NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight) {
131 : // We are in a paginated situation. The bottom edge is just inside
132 : // the bottom border and padding. The content area height doesn't
133 : // include either border or padding edge.
134 0 : mBottomEdge = aReflowState.availableHeight - borderPadding.bottom;
135 0 : mContentArea.height = NS_MAX(0, mBottomEdge - borderPadding.top);
136 : }
137 : else {
138 : // When we are not in a paginated situation then we always use
139 : // an constrained height.
140 0 : SetFlag(BRS_UNCONSTRAINEDHEIGHT, true);
141 0 : mContentArea.height = mBottomEdge = NS_UNCONSTRAINEDSIZE;
142 : }
143 0 : mContentArea.x = borderPadding.left;
144 0 : mY = mContentArea.y = borderPadding.top;
145 :
146 0 : mPrevChild = nsnull;
147 0 : mCurrentLine = aFrame->end_lines();
148 :
149 0 : mMinLineHeight = aReflowState.CalcLineHeight();
150 0 : }
151 :
152 : void
153 0 : nsBlockReflowState::ComputeReplacedBlockOffsetsForFloats(nsIFrame* aFrame,
154 : const nsRect& aFloatAvailableSpace,
155 : nscoord& aLeftResult,
156 : nscoord& aRightResult)
157 : {
158 : // The frame is clueless about the float manager and therefore we
159 : // only give it free space. An example is a table frame - the
160 : // tables do not flow around floats.
161 : // However, we can let its margins intersect floats.
162 0 : NS_ASSERTION(aFloatAvailableSpace.x >= mContentArea.x, "bad avail space rect x");
163 0 : NS_ASSERTION(aFloatAvailableSpace.width == 0 ||
164 : aFloatAvailableSpace.XMost() <= mContentArea.XMost(),
165 : "bad avail space rect width");
166 :
167 : nscoord leftOffset, rightOffset;
168 0 : if (aFloatAvailableSpace.width == mContentArea.width) {
169 : // We don't need to compute margins when there are no floats around.
170 0 : leftOffset = 0;
171 0 : rightOffset = 0;
172 : } else {
173 0 : nsMargin frameMargin;
174 0 : nsCSSOffsetState os(aFrame, mReflowState.rendContext, mContentArea.width);
175 0 : frameMargin = os.mComputedMargin;
176 :
177 0 : nscoord leftFloatXOffset = aFloatAvailableSpace.x - mContentArea.x;
178 0 : leftOffset = NS_MAX(leftFloatXOffset, frameMargin.left) -
179 0 : frameMargin.left;
180 0 : leftOffset = NS_MAX(leftOffset, 0); // in case of negative margin
181 : nscoord rightFloatXOffset =
182 0 : mContentArea.XMost() - aFloatAvailableSpace.XMost();
183 0 : rightOffset = NS_MAX(rightFloatXOffset, frameMargin.right) -
184 0 : frameMargin.right;
185 0 : rightOffset = NS_MAX(rightOffset, 0); // in case of negative margin
186 : }
187 0 : aLeftResult = leftOffset;
188 0 : aRightResult = rightOffset;
189 0 : }
190 :
191 : // Compute the amount of available space for reflowing a block frame
192 : // at the current Y coordinate. This method assumes that
193 : // GetAvailableSpace has already been called.
194 : void
195 0 : nsBlockReflowState::ComputeBlockAvailSpace(nsIFrame* aFrame,
196 : const nsStyleDisplay* aDisplay,
197 : const nsFlowAreaRect& aFloatAvailableSpace,
198 : bool aBlockAvoidsFloats,
199 : nsRect& aResult)
200 : {
201 : #ifdef REALLY_NOISY_REFLOW
202 : printf("CBAS frame=%p has floats %d\n",
203 : aFrame, aFloatAvailableSpace.mHasFloats);
204 : #endif
205 0 : aResult.y = mY;
206 0 : aResult.height = GetFlag(BRS_UNCONSTRAINEDHEIGHT)
207 : ? NS_UNCONSTRAINEDSIZE
208 0 : : mReflowState.availableHeight - mY;
209 : // mY might be greater than mBottomEdge if the block's top margin pushes
210 : // it off the page/column. Negative available height can confuse other code
211 : // and is nonsense in principle.
212 :
213 : // XXX Do we really want this condition to be this restrictive (i.e.,
214 : // more restrictive than it used to be)? The |else| here is allowed
215 : // by the CSS spec, but only out of desperation given implementations,
216 : // and the behavior it leads to is quite undesirable (it can cause
217 : // things to become extremely narrow when they'd fit quite well a
218 : // little bit lower). Should the else be a quirk or something that
219 : // applies to a specific set of frame classes and no new ones?
220 : // If we did that, then for those frames where the condition below is
221 : // true but nsBlockFrame::BlockCanIntersectFloats is false,
222 : // nsBlockFrame::WidthToClearPastFloats would need to use the
223 : // shrink-wrap formula, max(MIN_WIDTH, min(avail width, PREF_WIDTH))
224 : // rather than just using MIN_WIDTH.
225 0 : NS_ASSERTION(nsBlockFrame::BlockCanIntersectFloats(aFrame) ==
226 : !aBlockAvoidsFloats,
227 : "unexpected replaced width");
228 0 : if (!aBlockAvoidsFloats) {
229 0 : if (aFloatAvailableSpace.mHasFloats) {
230 : // Use the float-edge property to determine how the child block
231 : // will interact with the float.
232 0 : const nsStyleBorder* borderStyle = aFrame->GetStyleBorder();
233 0 : switch (borderStyle->mFloatEdge) {
234 : default:
235 : case NS_STYLE_FLOAT_EDGE_CONTENT: // content and only content does runaround of floats
236 : // The child block will flow around the float. Therefore
237 : // give it all of the available space.
238 0 : aResult.x = mContentArea.x;
239 0 : aResult.width = mContentArea.width;
240 0 : break;
241 : case NS_STYLE_FLOAT_EDGE_MARGIN:
242 : {
243 : // The child block's margins should be placed adjacent to,
244 : // but not overlap the float.
245 0 : aResult.x = aFloatAvailableSpace.mRect.x;
246 0 : aResult.width = aFloatAvailableSpace.mRect.width;
247 : }
248 0 : break;
249 : }
250 : }
251 : else {
252 : // Since there are no floats present the float-edge property
253 : // doesn't matter therefore give the block element all of the
254 : // available space since it will flow around the float itself.
255 0 : aResult.x = mContentArea.x;
256 0 : aResult.width = mContentArea.width;
257 : }
258 : }
259 : else {
260 : nscoord leftOffset, rightOffset;
261 : ComputeReplacedBlockOffsetsForFloats(aFrame, aFloatAvailableSpace.mRect,
262 0 : leftOffset, rightOffset);
263 0 : aResult.x = mContentArea.x + leftOffset;
264 0 : aResult.width = mContentArea.width - leftOffset - rightOffset;
265 : }
266 :
267 : #ifdef REALLY_NOISY_REFLOW
268 : printf(" CBAS: result %d %d %d %d\n", aResult.x, aResult.y, aResult.width, aResult.height);
269 : #endif
270 0 : }
271 :
272 : nsFlowAreaRect
273 0 : nsBlockReflowState::GetFloatAvailableSpaceWithState(
274 : nscoord aY,
275 : nsFloatManager::SavedState *aState) const
276 : {
277 : #ifdef DEBUG
278 : // Verify that the caller setup the coordinate system properly
279 : nscoord wx, wy;
280 0 : mFloatManager->GetTranslation(wx, wy);
281 0 : NS_ASSERTION((wx == mFloatManagerX) && (wy == mFloatManagerY),
282 : "bad coord system");
283 : #endif
284 :
285 : nscoord height = (mContentArea.height == nscoord_MAX)
286 0 : ? nscoord_MAX : NS_MAX(mContentArea.YMost() - aY, 0);
287 : nsFlowAreaRect result =
288 : mFloatManager->GetFlowArea(aY, nsFloatManager::BAND_FROM_POINT,
289 0 : height, mContentArea, aState);
290 : // Keep the width >= 0 for compatibility with nsSpaceManager.
291 0 : if (result.mRect.width < 0)
292 0 : result.mRect.width = 0;
293 :
294 : #ifdef DEBUG
295 0 : if (nsBlockFrame::gNoisyReflow) {
296 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
297 : printf("GetAvailableSpace: band=%d,%d,%d,%d hasfloats=%d\n",
298 : result.mRect.x, result.mRect.y, result.mRect.width,
299 0 : result.mRect.height, result.mHasFloats);
300 : }
301 : #endif
302 : return result;
303 : }
304 :
305 : nsFlowAreaRect
306 0 : nsBlockReflowState::GetFloatAvailableSpaceForHeight(
307 : nscoord aY, nscoord aHeight,
308 : nsFloatManager::SavedState *aState) const
309 : {
310 : #ifdef DEBUG
311 : // Verify that the caller setup the coordinate system properly
312 : nscoord wx, wy;
313 0 : mFloatManager->GetTranslation(wx, wy);
314 0 : NS_ASSERTION((wx == mFloatManagerX) && (wy == mFloatManagerY),
315 : "bad coord system");
316 : #endif
317 :
318 : nsFlowAreaRect result =
319 : mFloatManager->GetFlowArea(aY, nsFloatManager::WIDTH_WITHIN_HEIGHT,
320 0 : aHeight, mContentArea, aState);
321 : // Keep the width >= 0 for compatibility with nsSpaceManager.
322 0 : if (result.mRect.width < 0)
323 0 : result.mRect.width = 0;
324 :
325 : #ifdef DEBUG
326 0 : if (nsBlockFrame::gNoisyReflow) {
327 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
328 : printf("GetAvailableSpaceForHeight: space=%d,%d,%d,%d hasfloats=%d\n",
329 : result.mRect.x, result.mRect.y, result.mRect.width,
330 0 : result.mRect.height, result.mHasFloats);
331 : }
332 : #endif
333 : return result;
334 : }
335 :
336 : /*
337 : * Reconstruct the vertical margin before the line |aLine| in order to
338 : * do an incremental reflow that begins with |aLine| without reflowing
339 : * the line before it. |aLine| may point to the fencepost at the end of
340 : * the line list, and it is used this way since we (for now, anyway)
341 : * always need to recover margins at the end of a block.
342 : *
343 : * The reconstruction involves walking backward through the line list to
344 : * find any collapsed margins preceding the line that would have been in
345 : * the reflow state's |mPrevBottomMargin| when we reflowed that line in
346 : * a full reflow (under the rule in CSS2 that all adjacent vertical
347 : * margins of blocks collapse).
348 : */
349 : void
350 0 : nsBlockReflowState::ReconstructMarginAbove(nsLineList::iterator aLine)
351 : {
352 0 : mPrevBottomMargin.Zero();
353 0 : nsBlockFrame *block = mBlock;
354 :
355 0 : nsLineList::iterator firstLine = block->begin_lines();
356 0 : for (;;) {
357 0 : --aLine;
358 0 : if (aLine->IsBlock()) {
359 0 : mPrevBottomMargin = aLine->GetCarriedOutBottomMargin();
360 0 : break;
361 : }
362 0 : if (!aLine->IsEmpty()) {
363 0 : break;
364 : }
365 0 : if (aLine == firstLine) {
366 : // If the top margin was carried out (and thus already applied),
367 : // set it to zero. Either way, we're done.
368 0 : if (!GetFlag(BRS_ISTOPMARGINROOT)) {
369 0 : mPrevBottomMargin.Zero();
370 : }
371 0 : break;
372 : }
373 : }
374 0 : }
375 :
376 : void
377 0 : nsBlockReflowState::SetupPushedFloatList()
378 : {
379 0 : NS_ABORT_IF_FALSE(!GetFlag(BRS_PROPTABLE_FLOATCLIST) == !mPushedFloats,
380 : "flag mismatch");
381 0 : if (!GetFlag(BRS_PROPTABLE_FLOATCLIST)) {
382 : // If we're being re-Reflow'd without our next-in-flow having been
383 : // reflowed, some pushed floats from our previous reflow might
384 : // still be on our pushed floats list. However, that's
385 : // actually fine, since they'll all end up being stolen and
386 : // reordered into the correct order again.
387 : // (nsBlockFrame::ReflowDirtyLines ensures that any lines with
388 : // pushed floats are reflowed.)
389 0 : mPushedFloats = mBlock->EnsurePushedFloats();
390 0 : SetFlag(BRS_PROPTABLE_FLOATCLIST, true);
391 : }
392 0 : }
393 :
394 : /**
395 : * Restore information about floats into the float manager for an
396 : * incremental reflow, and simultaneously push the floats by
397 : * |aDeltaY|, which is the amount |aLine| was pushed relative to its
398 : * parent. The recovery of state is one of the things that makes
399 : * incremental reflow O(N^2) and this state should really be kept
400 : * around, attached to the frame tree.
401 : */
402 : void
403 0 : nsBlockReflowState::RecoverFloats(nsLineList::iterator aLine,
404 : nscoord aDeltaY)
405 : {
406 0 : if (aLine->HasFloats()) {
407 : // Place the floats into the space-manager again. Also slide
408 : // them, just like the regular frames on the line.
409 0 : nsFloatCache* fc = aLine->GetFirstFloat();
410 0 : while (fc) {
411 0 : nsIFrame* floatFrame = fc->mFloat;
412 0 : if (aDeltaY != 0) {
413 0 : nsPoint p = floatFrame->GetPosition();
414 0 : floatFrame->SetPosition(nsPoint(p.x, p.y + aDeltaY));
415 0 : nsContainerFrame::PositionFrameView(floatFrame);
416 0 : nsContainerFrame::PositionChildViews(floatFrame);
417 : }
418 : #ifdef DEBUG
419 0 : if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
420 : nscoord tx, ty;
421 0 : mFloatManager->GetTranslation(tx, ty);
422 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
423 : printf("RecoverFloats: txy=%d,%d (%d,%d) ",
424 0 : tx, ty, mFloatManagerX, mFloatManagerY);
425 0 : nsFrame::ListTag(stdout, floatFrame);
426 0 : nsRect region = nsFloatManager::GetRegionFor(floatFrame);
427 : printf(" aDeltaY=%d region={%d,%d,%d,%d}\n",
428 0 : aDeltaY, region.x, region.y, region.width, region.height);
429 : }
430 : #endif
431 : mFloatManager->AddFloat(floatFrame,
432 0 : nsFloatManager::GetRegionFor(floatFrame));
433 0 : fc = fc->Next();
434 : }
435 0 : } else if (aLine->IsBlock()) {
436 0 : nsBlockFrame::RecoverFloatsFor(aLine->mFirstChild, *mFloatManager);
437 : }
438 0 : }
439 :
440 : /**
441 : * Everything done in this function is done O(N) times for each pass of
442 : * reflow so it is O(N*M) where M is the number of incremental reflow
443 : * passes. That's bad. Don't do stuff here.
444 : *
445 : * When this function is called, |aLine| has just been slid by |aDeltaY|
446 : * and the purpose of RecoverStateFrom is to ensure that the
447 : * nsBlockReflowState is in the same state that it would have been in
448 : * had the line just been reflowed.
449 : *
450 : * Most of the state recovery that we have to do involves floats.
451 : */
452 : void
453 0 : nsBlockReflowState::RecoverStateFrom(nsLineList::iterator aLine,
454 : nscoord aDeltaY)
455 : {
456 : // Make the line being recovered the current line
457 0 : mCurrentLine = aLine;
458 :
459 : // Place floats for this line into the float manager
460 0 : if (aLine->HasFloats() || aLine->IsBlock()) {
461 0 : RecoverFloats(aLine, aDeltaY);
462 :
463 : #ifdef DEBUG
464 0 : if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisyFloatManager) {
465 0 : mFloatManager->List(stdout);
466 : }
467 : #endif
468 : }
469 0 : }
470 :
471 : // This is called by the line layout's AddFloat method when a
472 : // place-holder frame is reflowed in a line. If the float is a
473 : // left-most child (it's x coordinate is at the line's left margin)
474 : // then the float is place immediately, otherwise the float
475 : // placement is deferred until the line has been reflowed.
476 :
477 : // XXXldb This behavior doesn't quite fit with CSS1 and CSS2 --
478 : // technically we're supposed let the current line flow around the
479 : // float as well unless it won't fit next to what we already have.
480 : // But nobody else implements it that way...
481 : bool
482 0 : nsBlockReflowState::AddFloat(nsLineLayout* aLineLayout,
483 : nsIFrame* aFloat,
484 : nscoord aAvailableWidth)
485 : {
486 0 : NS_PRECONDITION(aLineLayout, "must have line layout");
487 0 : NS_PRECONDITION(mBlock->end_lines() != mCurrentLine, "null ptr");
488 0 : NS_PRECONDITION(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW,
489 : "aFloat must be an out-of-flow frame");
490 :
491 0 : NS_ABORT_IF_FALSE(aFloat->GetParent(), "float must have parent");
492 0 : NS_ABORT_IF_FALSE(aFloat->GetParent()->IsFrameOfType(nsIFrame::eBlockFrame),
493 : "float's parent must be block");
494 0 : NS_ABORT_IF_FALSE(aFloat->GetParent() == mBlock ||
495 : (aFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
496 : "float should be in this block unless it was marked as "
497 : "pushed float");
498 0 : if (aFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT) {
499 : // If, in a previous reflow, the float was pushed entirely to
500 : // another column/page, we need to steal it back. (We might just
501 : // push it again, though.) Likewise, if that previous reflow
502 : // reflowed this block but not its next continuation, we might need
503 : // to steal it from our own float-continuations list.
504 : nsBlockFrame *floatParent =
505 0 : static_cast<nsBlockFrame*>(aFloat->GetParent());
506 0 : floatParent->StealFrame(mPresContext, aFloat);
507 :
508 0 : aFloat->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
509 :
510 : // Appending is fine, since if a float was pushed to the next
511 : // page/column, all later floats were also pushed.
512 0 : mBlock->mFloats.AppendFrame(mBlock, aFloat);
513 : }
514 :
515 : // Because we are in the middle of reflowing a placeholder frame
516 : // within a line (and possibly nested in an inline frame or two
517 : // that's a child of our block) we need to restore the space
518 : // manager's translation to the space that the block resides in
519 : // before placing the float.
520 : nscoord ox, oy;
521 0 : mFloatManager->GetTranslation(ox, oy);
522 0 : nscoord dx = ox - mFloatManagerX;
523 0 : nscoord dy = oy - mFloatManagerY;
524 0 : mFloatManager->Translate(-dx, -dy);
525 :
526 : bool placed;
527 :
528 : // Now place the float immediately if possible. Otherwise stash it
529 : // away in mPendingFloats and place it later.
530 : // If one or more floats has already been pushed to the next line,
531 : // don't let this one go on the current line, since that would violate
532 : // float ordering.
533 0 : nsRect floatAvailableSpace = GetFloatAvailableSpace().mRect;
534 0 : if (mBelowCurrentLineFloats.IsEmpty() &&
535 0 : (aLineLayout->LineIsEmpty() ||
536 0 : mBlock->ComputeFloatWidth(*this, floatAvailableSpace, aFloat)
537 : <= aAvailableWidth)) {
538 : // And then place it
539 0 : placed = FlowAndPlaceFloat(aFloat);
540 0 : if (placed) {
541 : // Pass on updated available space to the current inline reflow engine
542 0 : nsFlowAreaRect floatAvailSpace = GetFloatAvailableSpace(mY);
543 : nsRect availSpace(nsPoint(floatAvailSpace.mRect.x, mY),
544 0 : floatAvailSpace.mRect.Size());
545 0 : aLineLayout->UpdateBand(availSpace, aFloat);
546 : // Record this float in the current-line list
547 0 : mCurrentLineFloats.Append(mFloatCacheFreeList.Alloc(aFloat));
548 : } else {
549 0 : (*aLineLayout->GetLine())->SetHadFloatPushed();
550 : }
551 : }
552 : else {
553 : // Always claim to be placed; we don't know whether we fit yet, so we
554 : // deal with this in PlaceBelowCurrentLineFloats
555 0 : placed = true;
556 : // This float will be placed after the line is done (it is a
557 : // below-current-line float).
558 0 : mBelowCurrentLineFloats.Append(mFloatCacheFreeList.Alloc(aFloat));
559 : }
560 :
561 : // Restore coordinate system
562 0 : mFloatManager->Translate(dx, dy);
563 :
564 0 : return placed;
565 : }
566 :
567 : bool
568 0 : nsBlockReflowState::CanPlaceFloat(nscoord aFloatWidth,
569 : const nsFlowAreaRect& aFloatAvailableSpace)
570 : {
571 : // A float fits at a given vertical position if there are no floats at
572 : // its horizontal position (no matter what its width) or if its width
573 : // fits in the space remaining after prior floats have been placed.
574 : // FIXME: We should allow overflow by up to half a pixel here (bug 21193).
575 0 : return !aFloatAvailableSpace.mHasFloats ||
576 0 : aFloatAvailableSpace.mRect.width >= aFloatWidth;
577 : }
578 :
579 : static nscoord
580 0 : FloatMarginWidth(const nsHTMLReflowState& aCBReflowState,
581 : nscoord aFloatAvailableWidth,
582 : nsIFrame *aFloat,
583 : const nsCSSOffsetState& aFloatOffsetState)
584 : {
585 0 : AutoMaybeNullInflationContainer an(aFloat);
586 : return aFloat->ComputeSize(
587 : aCBReflowState.rendContext,
588 : nsSize(aCBReflowState.ComputedWidth(),
589 : aCBReflowState.ComputedHeight()),
590 : aFloatAvailableWidth,
591 : nsSize(aFloatOffsetState.mComputedMargin.LeftRight(),
592 : aFloatOffsetState.mComputedMargin.TopBottom()),
593 0 : nsSize(aFloatOffsetState.mComputedBorderPadding.LeftRight() -
594 0 : aFloatOffsetState.mComputedPadding.LeftRight(),
595 0 : aFloatOffsetState.mComputedBorderPadding.TopBottom() -
596 0 : aFloatOffsetState.mComputedPadding.TopBottom()),
597 : nsSize(aFloatOffsetState.mComputedPadding.LeftRight(),
598 : aFloatOffsetState.mComputedPadding.TopBottom()),
599 0 : true).width +
600 0 : aFloatOffsetState.mComputedMargin.LeftRight() +
601 0 : aFloatOffsetState.mComputedBorderPadding.LeftRight();
602 : }
603 :
604 : bool
605 0 : nsBlockReflowState::FlowAndPlaceFloat(nsIFrame* aFloat)
606 : {
607 : // Save away the Y coordinate before placing the float. We will
608 : // restore mY at the end after placing the float. This is
609 : // necessary because any adjustments to mY during the float
610 : // placement are for the float only, not for any non-floating
611 : // content.
612 0 : AutoRestore<nscoord> restoreY(mY);
613 : // FIXME: Should give AutoRestore a getter for the value to avoid this.
614 0 : const nscoord saveY = mY;
615 :
616 : // Grab the float's display information
617 0 : const nsStyleDisplay* floatDisplay = aFloat->GetStyleDisplay();
618 :
619 : // The float's old region, so we can propagate damage.
620 0 : nsRect oldRegion = nsFloatManager::GetRegionFor(aFloat);
621 :
622 : // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't
623 : // ``above'' another float that preceded it in the flow.
624 0 : mY = NS_MAX(mFloatManager->GetLowestFloatTop(), mY);
625 :
626 : // See if the float should clear any preceding floats...
627 : // XXX We need to mark this float somehow so that it gets reflowed
628 : // when floats are inserted before it.
629 0 : if (NS_STYLE_CLEAR_NONE != floatDisplay->mBreakType) {
630 : // XXXldb Does this handle vertical margins correctly?
631 0 : mY = ClearFloats(mY, floatDisplay->mBreakType);
632 : }
633 : // Get the band of available space
634 0 : nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(mY);
635 : nsRect adjustedAvailableSpace = mBlock->AdjustFloatAvailableSpace(*this,
636 0 : floatAvailableSpace.mRect, aFloat);
637 :
638 0 : NS_ASSERTION(aFloat->GetParent() == mBlock,
639 : "Float frame has wrong parent");
640 :
641 : nsCSSOffsetState offsets(aFloat, mReflowState.rendContext,
642 0 : mReflowState.ComputedWidth());
643 :
644 : nscoord floatMarginWidth = FloatMarginWidth(mReflowState,
645 : adjustedAvailableSpace.width,
646 0 : aFloat, offsets);
647 :
648 0 : nsMargin floatMargin; // computed margin
649 : nsReflowStatus reflowStatus;
650 :
651 : // If it's a floating first-letter, we need to reflow it before we
652 : // know how wide it is (since we don't compute which letters are part
653 : // of the first letter until reflow!).
654 0 : bool isLetter = aFloat->GetType() == nsGkAtoms::letterFrame;
655 0 : if (isLetter) {
656 : mBlock->ReflowFloat(*this, adjustedAvailableSpace, aFloat,
657 0 : floatMargin, false, reflowStatus);
658 0 : floatMarginWidth = aFloat->GetSize().width + floatMargin.LeftRight();
659 0 : NS_ASSERTION(NS_FRAME_IS_COMPLETE(reflowStatus),
660 : "letter frames shouldn't break, and if they do now, "
661 : "then they're breaking at the wrong point");
662 : }
663 :
664 : // Find a place to place the float. The CSS2 spec doesn't want
665 : // floats overlapping each other or sticking out of the containing
666 : // block if possible (CSS2 spec section 9.5.1, see the rule list).
667 0 : NS_ASSERTION((NS_STYLE_FLOAT_LEFT == floatDisplay->mFloats) ||
668 : (NS_STYLE_FLOAT_RIGHT == floatDisplay->mFloats),
669 : "invalid float type");
670 :
671 : // Can the float fit here?
672 0 : bool keepFloatOnSameLine = false;
673 :
674 : // Are we required to place at least part of the float because we're
675 : // at the top of the page (to avoid an infinite loop of pushing and
676 : // breaking).
677 : bool mustPlaceFloat =
678 0 : mReflowState.mFlags.mIsTopOfPage && IsAdjacentWithTop();
679 :
680 0 : for (;;) {
681 0 : if (mReflowState.availableHeight != NS_UNCONSTRAINEDSIZE &&
682 : floatAvailableSpace.mRect.height <= 0 &&
683 0 : !mustPlaceFloat) {
684 : // No space, nowhere to put anything.
685 0 : PushFloatPastBreak(aFloat);
686 0 : return false;
687 : }
688 :
689 0 : if (CanPlaceFloat(floatMarginWidth, floatAvailableSpace)) {
690 : // We found an appropriate place.
691 0 : break;
692 : }
693 :
694 : // Nope. try to advance to the next band.
695 0 : if (NS_STYLE_DISPLAY_TABLE != floatDisplay->mDisplay ||
696 0 : eCompatibility_NavQuirks != mPresContext->CompatibilityMode() ) {
697 :
698 0 : mY += floatAvailableSpace.mRect.height;
699 0 : if (adjustedAvailableSpace.height != NS_UNCONSTRAINEDSIZE) {
700 0 : adjustedAvailableSpace.height -= floatAvailableSpace.mRect.height;
701 : }
702 0 : floatAvailableSpace = GetFloatAvailableSpace(mY);
703 : } else {
704 : // This quirk matches the one in nsBlockFrame::AdjustFloatAvailableSpace
705 : // IE handles float tables in a very special way
706 :
707 : // see if the previous float is also a table and has "align"
708 0 : nsFloatCache* fc = mCurrentLineFloats.Head();
709 0 : nsIFrame* prevFrame = nsnull;
710 0 : while (fc) {
711 0 : if (fc->mFloat == aFloat) {
712 0 : break;
713 : }
714 0 : prevFrame = fc->mFloat;
715 0 : fc = fc->Next();
716 : }
717 :
718 0 : if(prevFrame) {
719 : //get the frame type
720 0 : if (nsGkAtoms::tableOuterFrame == prevFrame->GetType()) {
721 : //see if it has "align="
722 : // IE makes a difference between align and he float property
723 0 : nsIContent* content = prevFrame->GetContent();
724 0 : if (content) {
725 : // we're interested only if previous frame is align=left
726 : // IE messes things up when "right" (overlapping frames)
727 0 : if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::align,
728 0 : NS_LITERAL_STRING("left"), eIgnoreCase)) {
729 0 : keepFloatOnSameLine = true;
730 : // don't advance to next line (IE quirkie behaviour)
731 : // it breaks rule CSS2/9.5.1/1, but what the hell
732 : // since we cannot evangelize the world
733 0 : break;
734 : }
735 : }
736 : }
737 : }
738 :
739 : // the table does not fit anymore in this line so advance to next band
740 0 : mY += floatAvailableSpace.mRect.height;
741 : // To match nsBlockFrame::AdjustFloatAvailableSpace, we have to
742 : // get a new width for the new band.
743 0 : floatAvailableSpace = GetFloatAvailableSpace(mY);
744 : adjustedAvailableSpace = mBlock->AdjustFloatAvailableSpace(*this,
745 0 : floatAvailableSpace.mRect, aFloat);
746 : floatMarginWidth = FloatMarginWidth(mReflowState,
747 : adjustedAvailableSpace.width,
748 0 : aFloat, offsets);
749 : }
750 :
751 0 : mustPlaceFloat = false;
752 : }
753 :
754 : // If the float is continued, it will get the same absolute x value as its prev-in-flow
755 :
756 : // We don't worry about the geometry of the prev in flow, let the continuation
757 : // place and size itself as required.
758 :
759 : // Assign an x and y coordinate to the float.
760 : nscoord floatX, floatY;
761 0 : if (NS_STYLE_FLOAT_LEFT == floatDisplay->mFloats) {
762 0 : floatX = floatAvailableSpace.mRect.x;
763 : }
764 : else {
765 0 : if (!keepFloatOnSameLine) {
766 0 : floatX = floatAvailableSpace.mRect.XMost() - floatMarginWidth;
767 : }
768 : else {
769 : // this is the IE quirk (see few lines above)
770 : // the table is kept in the same line: don't let it overlap the
771 : // previous float
772 0 : floatX = floatAvailableSpace.mRect.x;
773 : }
774 : }
775 : // CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not
776 : // be higher than the top of its containing block." (Since the
777 : // containing block is the content edge of the block box, this
778 : // means the margin edge of the float can't be higher than the
779 : // content edge of the block that contains it.)
780 0 : floatY = NS_MAX(mY, mContentArea.y);
781 :
782 : // Reflow the float after computing its vertical position so it knows
783 : // where to break.
784 0 : if (!isLetter) {
785 0 : bool pushedDown = mY != saveY;
786 : mBlock->ReflowFloat(*this, adjustedAvailableSpace, aFloat,
787 0 : floatMargin, pushedDown, reflowStatus);
788 : }
789 0 : if (aFloat->GetPrevInFlow())
790 0 : floatMargin.top = 0;
791 0 : if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus))
792 0 : floatMargin.bottom = 0;
793 :
794 : // In the case that we're in columns and not splitting floats, we need
795 : // to check here that the float's height fit, and if it didn't, bail.
796 : // (This code is only for DISABLE_FLOAT_BREAKING_IN_COLUMNS .)
797 : //
798 : // Likewise, if none of the float fit, and it needs to be pushed in
799 : // its entirety to the next page (NS_FRAME_IS_TRUNCATED), we need to
800 : // do the same.
801 0 : if ((mContentArea.height != NS_UNCONSTRAINEDSIZE &&
802 : adjustedAvailableSpace.height == NS_UNCONSTRAINEDSIZE &&
803 0 : !mustPlaceFloat &&
804 0 : aFloat->GetSize().height + floatMargin.TopBottom() >
805 0 : mContentArea.YMost() - floatY) ||
806 : NS_FRAME_IS_TRUNCATED(reflowStatus)) {
807 :
808 0 : PushFloatPastBreak(aFloat);
809 0 : return false;
810 : }
811 :
812 : // Calculate the actual origin of the float frame's border rect
813 : // relative to the parent block; the margin must be added in
814 : // to get the border rect
815 : nsPoint origin(floatMargin.left + floatX,
816 0 : floatMargin.top + floatY);
817 :
818 : // If float is relatively positioned, factor that in as well
819 0 : origin += aFloat->GetRelativeOffset(floatDisplay);
820 :
821 : // Position the float and make sure and views are properly
822 : // positioned. We need to explicitly position its child views as
823 : // well, since we're moving the float after flowing it.
824 0 : bool moved = aFloat->GetPosition() != origin;
825 0 : if (moved) {
826 0 : aFloat->SetPosition(origin);
827 0 : nsContainerFrame::PositionFrameView(aFloat);
828 0 : nsContainerFrame::PositionChildViews(aFloat);
829 0 : FrameLayerBuilder::InvalidateThebesLayersInSubtree(aFloat);
830 : }
831 :
832 : // Update the float combined area state
833 : // XXX Floats should really just get invalidated here if necessary
834 0 : mFloatOverflowAreas.UnionWith(aFloat->GetOverflowAreas() + origin);
835 :
836 : // Place the float in the float manager
837 : // calculate region
838 0 : nsRect region = nsFloatManager::CalculateRegionFor(aFloat, floatMargin);
839 : // if the float split, then take up all of the vertical height
840 0 : if (NS_FRAME_IS_NOT_COMPLETE(reflowStatus) &&
841 : (NS_UNCONSTRAINEDSIZE != mContentArea.height)) {
842 0 : region.height = NS_MAX(region.height, mContentArea.height - floatY);
843 : }
844 : nsresult rv =
845 0 : mFloatManager->AddFloat(aFloat, region);
846 0 : NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "bad float placement");
847 : // store region
848 0 : nsFloatManager::StoreRegionFor(aFloat, region);
849 :
850 : // If the float's dimensions have changed, note the damage in the
851 : // float manager.
852 0 : if (!region.IsEqualEdges(oldRegion)) {
853 : // XXXwaterson conservative: we could probably get away with noting
854 : // less damage; e.g., if only height has changed, then only note the
855 : // area into which the float has grown or from which the float has
856 : // shrunk.
857 0 : nscoord top = NS_MIN(region.y, oldRegion.y);
858 0 : nscoord bottom = NS_MAX(region.YMost(), oldRegion.YMost());
859 0 : mFloatManager->IncludeInDamage(top, bottom);
860 : }
861 :
862 0 : if (!NS_FRAME_IS_FULLY_COMPLETE(reflowStatus)) {
863 0 : mBlock->SplitFloat(*this, aFloat, reflowStatus);
864 : }
865 :
866 : #ifdef NOISY_FLOATMANAGER
867 : nscoord tx, ty;
868 : mFloatManager->GetTranslation(tx, ty);
869 : nsFrame::ListTag(stdout, mBlock);
870 : printf(": FlowAndPlaceFloat: AddFloat: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
871 : tx, ty, mFloatManagerX, mFloatManagerY,
872 : region.x, region.y, region.width, region.height);
873 : #endif
874 :
875 : #ifdef DEBUG
876 0 : if (nsBlockFrame::gNoisyReflow) {
877 0 : nsRect r = aFloat->GetRect();
878 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
879 0 : printf("placed float: ");
880 0 : nsFrame::ListTag(stdout, aFloat);
881 0 : printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height);
882 : }
883 : #endif
884 :
885 0 : return true;
886 : }
887 :
888 : void
889 0 : nsBlockReflowState::PushFloatPastBreak(nsIFrame *aFloat)
890 : {
891 : // This ensures that we:
892 : // * don't try to place later but smaller floats (which CSS says
893 : // must have their tops below the top of this float)
894 : // * don't waste much time trying to reflow this float again until
895 : // after the break
896 0 : if (aFloat->GetStyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) {
897 0 : mFloatManager->SetPushedLeftFloatPastBreak();
898 : } else {
899 0 : NS_ABORT_IF_FALSE(aFloat->GetStyleDisplay()->mFloats ==
900 : NS_STYLE_FLOAT_RIGHT,
901 : "unexpected float value");
902 0 : mFloatManager->SetPushedRightFloatPastBreak();
903 : }
904 :
905 : // Put the float on the pushed floats list, even though it
906 : // isn't actually a continuation.
907 0 : DebugOnly<nsresult> rv = mBlock->StealFrame(mPresContext, aFloat);
908 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "StealFrame should succeed");
909 0 : AppendPushedFloat(aFloat);
910 :
911 0 : NS_FRAME_SET_OVERFLOW_INCOMPLETE(mReflowStatus);
912 0 : }
913 :
914 : /**
915 : * Place below-current-line floats.
916 : */
917 : void
918 0 : nsBlockReflowState::PlaceBelowCurrentLineFloats(nsFloatCacheFreeList& aList,
919 : nsLineBox* aLine)
920 : {
921 0 : nsFloatCache* fc = aList.Head();
922 0 : while (fc) {
923 : #ifdef DEBUG
924 0 : if (nsBlockFrame::gNoisyReflow) {
925 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
926 0 : printf("placing bcl float: ");
927 0 : nsFrame::ListTag(stdout, fc->mFloat);
928 0 : printf("\n");
929 : }
930 : #endif
931 : // Place the float
932 0 : bool placed = FlowAndPlaceFloat(fc->mFloat);
933 0 : nsFloatCache *next = fc->Next();
934 0 : if (!placed) {
935 0 : aList.Remove(fc);
936 0 : delete fc;
937 0 : aLine->SetHadFloatPushed();
938 : }
939 0 : fc = next;
940 : }
941 0 : }
942 :
943 : nscoord
944 0 : nsBlockReflowState::ClearFloats(nscoord aY, PRUint8 aBreakType,
945 : nsIFrame *aReplacedBlock,
946 : PRUint32 aFlags)
947 : {
948 : #ifdef DEBUG
949 0 : if (nsBlockFrame::gNoisyReflow) {
950 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
951 0 : printf("clear floats: in: aY=%d\n", aY);
952 : }
953 : #endif
954 :
955 : #ifdef NOISY_FLOAT_CLEARING
956 : printf("nsBlockReflowState::ClearFloats: aY=%d breakType=%d\n",
957 : aY, aBreakType);
958 : mFloatManager->List(stdout);
959 : #endif
960 :
961 0 : nscoord newY = aY;
962 :
963 0 : if (aBreakType != NS_STYLE_CLEAR_NONE) {
964 0 : newY = mFloatManager->ClearFloats(newY, aBreakType, aFlags);
965 : }
966 :
967 0 : if (aReplacedBlock) {
968 0 : for (;;) {
969 0 : nsFlowAreaRect floatAvailableSpace = GetFloatAvailableSpace(newY);
970 : nsBlockFrame::ReplacedElementWidthToClear replacedWidth =
971 : nsBlockFrame::WidthToClearPastFloats(*this, floatAvailableSpace.mRect,
972 0 : aReplacedBlock);
973 0 : if (!floatAvailableSpace.mHasFloats ||
974 : NS_MAX(floatAvailableSpace.mRect.x - mContentArea.x,
975 0 : replacedWidth.marginLeft) +
976 : replacedWidth.borderBoxWidth +
977 0 : NS_MAX(mContentArea.XMost() - floatAvailableSpace.mRect.XMost(),
978 0 : replacedWidth.marginRight) <=
979 : mContentArea.width) {
980 : break;
981 : }
982 : // See the analogous code for inlines in nsBlockFrame::DoReflowInlineFrames
983 0 : if (floatAvailableSpace.mRect.height > 0) {
984 : // See if there's room in the next band.
985 0 : newY += floatAvailableSpace.mRect.height;
986 : } else {
987 0 : if (mReflowState.availableHeight != NS_UNCONSTRAINEDSIZE) {
988 : // Stop trying to clear here; we'll just get pushed to the
989 : // next column or page and try again there.
990 : break;
991 : }
992 0 : NS_NOTREACHED("avail space rect with zero height!");
993 0 : newY += 1;
994 : }
995 : }
996 : }
997 :
998 : #ifdef DEBUG
999 0 : if (nsBlockFrame::gNoisyReflow) {
1000 0 : nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
1001 0 : printf("clear floats: out: y=%d\n", newY);
1002 : }
1003 : #endif
1004 :
1005 0 : return newY;
1006 : }
1007 :
|