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