layout/generic/nsBlockReflowState.cpp

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

mercurial