Files
tubestation/layout/generic/nsBlockReflowState.cpp
dbaron@fas.harvard.edu 4b1de618fc Bug 86947:
Make the line list doubly linked and access it through a list class and iterators.
Stop recomputing margins on all of the children of each block in the reflow chain (which causes O(N^2) state recovery during incremental reflow).  Instead, add a second dirty bit to the lines and walk backwards through the line list to recompute vertical margins only when either dirty bit is set and the previous line was not reflowed.  Add nsIFrame::IsEmpty to identify frames through which margins collapse.
Fix O(N^2) propagation of float damage by maintaining a set of intervals damaged by floats (bug 61962) and be sure to damage the correct areas (bug 48138).
Introduce nsCollapsingMargin to do correct collapsing of combinations of positive and negative margins (bug 50142).
Clean up some odds and ends and fix another smaller O(N^2) problem in nsBlockFrame::AddFrames.
r=attinasi, rbs  sr=waterson
2001-10-25 01:08:40 +00:00

1037 lines
36 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:cindent:ts=2:et:sw=2:
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Steve Clark <buster@netscape.com>
* Robert O'Callahan <roc+moz@cs.cmu.edu>
* L. David Baron <dbaron@fas.harvard.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsBlockReflowContext.h"
#include "nsBlockReflowState.h"
#include "nsBlockFrame.h"
#include "nsLineLayout.h"
#include "nsIPresContext.h"
#include "nsLayoutAtoms.h"
#include "nsIFrame.h"
#ifdef DEBUG
#include "nsBlockDebugFlags.h"
#endif
nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState,
nsIPresContext* aPresContext,
nsBlockFrame* aFrame,
const nsHTMLReflowMetrics& aMetrics,
PRBool aBlockMarginRoot)
: mBlock(aFrame),
mPresContext(aPresContext),
mReflowState(aReflowState),
mLastFloaterY(0),
mNextRCFrame(nsnull),
mPrevBottomMargin(),
mLineNumber(0),
mFlags(0)
{
const nsMargin& borderPadding = BorderPadding();
if (aBlockMarginRoot) {
SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE);
SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE);
}
if (0 != aReflowState.mComputedBorderPadding.top) {
SetFlag(BRS_ISTOPMARGINROOT, PR_TRUE);
}
if (0 != aReflowState.mComputedBorderPadding.bottom) {
SetFlag(BRS_ISBOTTOMMARGINROOT, PR_TRUE);
}
if (GetFlag(BRS_ISTOPMARGINROOT)) {
SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE);
}
mSpaceManager = aReflowState.mSpaceManager;
NS_ASSERTION( nsnull != mSpaceManager, "SpaceManager should be set in nsBlockReflowState" );
if( nsnull != mSpaceManager ) {
// Translate into our content area and then save the
// coordinate system origin for later.
mSpaceManager->Translate(borderPadding.left, borderPadding.top);
mSpaceManager->GetTranslation(mSpaceManagerX, mSpaceManagerY);
}
mReflowStatus = NS_FRAME_COMPLETE;
mPresContext = aPresContext;
mBlock->GetNextInFlow(NS_REINTERPRET_CAST(nsIFrame**, &mNextInFlow));
mKidXMost = 0;
// Compute content area width (the content area is inside the border
// and padding)
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedWidth) {
mContentArea.width = aReflowState.mComputedWidth;
}
else {
if (NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth) {
mContentArea.width = NS_UNCONSTRAINEDSIZE;
SetFlag(BRS_UNCONSTRAINEDWIDTH, PR_TRUE);
}
else if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
// Choose a width based on the content (shrink wrap width) up
// to the maximum width
mContentArea.width = aReflowState.mComputedMaxWidth;
SetFlag(BRS_SHRINKWRAPWIDTH, PR_TRUE);
}
else {
nscoord lr = borderPadding.left + borderPadding.right;
mContentArea.width = aReflowState.availableWidth - lr;
}
}
mHaveRightFloaters = PR_FALSE;
// Compute content area height. Unlike the width, if we have a
// specified style height we ignore it since extra content is
// managed by the "overflow" property. When we don't have a
// specified style height then we may end up limiting our height if
// the availableHeight is constrained (this situation occurs when we
// are paginated).
if (NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight) {
// We are in a paginated situation. The bottom edge is just inside
// the bottom border and padding. The content area height doesn't
// include either border or padding edge.
mBottomEdge = aReflowState.availableHeight - borderPadding.bottom;
mContentArea.height = mBottomEdge - borderPadding.top;
}
else {
// When we are not in a paginated situation then we always use
// an constrained height.
SetFlag(BRS_UNCONSTRAINEDHEIGHT, PR_TRUE);
mContentArea.height = mBottomEdge = NS_UNCONSTRAINEDSIZE;
}
mY = borderPadding.top;
mBand.Init(mSpaceManager, mContentArea);
mPrevChild = nsnull;
mCurrentLine = aFrame->end_lines();
const nsStyleText* styleText;
mBlock->GetStyleData(eStyleStruct_Text,
(const nsStyleStruct*&) styleText);
switch (styleText->mWhiteSpace) {
case NS_STYLE_WHITESPACE_PRE:
case NS_STYLE_WHITESPACE_NOWRAP:
SetFlag(BRS_NOWRAP, PR_TRUE);
break;
default:
SetFlag(BRS_NOWRAP, PR_FALSE);
break;
}
SetFlag(BRS_COMPUTEMAXELEMENTSIZE, (nsnull != aMetrics.maxElementSize));
#ifdef NOISY_MAX_ELEMENT_SIZE
printf("BRS: setting compute-MES to %d\n", (nsnull != aMetrics.maxElementSize));
#endif
mMaxElementSize.SizeTo(0, 0);
SetFlag(BRS_COMPUTEMAXWIDTH,
(NS_REFLOW_CALC_MAX_WIDTH == (aMetrics.mFlags & NS_REFLOW_CALC_MAX_WIDTH)));
mMaximumWidth = 0;
mMinLineHeight = nsHTMLReflowState::CalcLineHeight(mPresContext,
aReflowState.rendContext,
aReflowState.frame);
}
nsBlockReflowState::~nsBlockReflowState()
{
// Restore the coordinate system
const nsMargin& borderPadding = BorderPadding();
mSpaceManager->Translate(-borderPadding.left, -borderPadding.top);
}
nsLineBox*
nsBlockReflowState::NewLineBox(nsIFrame* aFrame,
PRInt32 aCount,
PRBool aIsBlock)
{
nsCOMPtr<nsIPresShell> shell;
mPresContext->GetShell(getter_AddRefs(shell));
return NS_NewLineBox(shell, aFrame, aCount, aIsBlock);
}
void
nsBlockReflowState::FreeLineBox(nsLineBox* aLine)
{
if (aLine) {
nsCOMPtr<nsIPresShell> presShell;
mPresContext->GetShell(getter_AddRefs(presShell));
aLine->Destroy(presShell);
}
}
// Compute the amount of available space for reflowing a block frame
// at the current Y coordinate. This method assumes that
// GetAvailableSpace has already been called.
void
nsBlockReflowState::ComputeBlockAvailSpace(nsIFrame* aFrame,
nsSplittableType aSplitType,
const nsStyleDisplay* aDisplay,
nsRect& aResult)
{
#ifdef REALLY_NOISY_REFLOW
printf("CBAS frame=%p has floater count %d\n", aFrame, mBand.GetFloaterCount());
mBand.List();
#endif
aResult.y = mY;
aResult.height = GetFlag(BRS_UNCONSTRAINEDHEIGHT)
? NS_UNCONSTRAINEDSIZE
: mBottomEdge - mY;
const nsMargin& borderPadding = BorderPadding();
/* bug 18445: treat elements mapped to display: block such as text controls
* just like normal blocks */
PRBool treatAsNotSplittable=PR_FALSE;
nsCOMPtr<nsIAtom>frameType;
aFrame->GetFrameType(getter_AddRefs(frameType));
if (frameType) {
// text controls are not splittable, so make a special case here
// XXXldb Why not just set the frame state bit?
if (nsLayoutAtoms::textInputFrame == frameType.get())
treatAsNotSplittable = PR_TRUE;
}
if (NS_FRAME_SPLITTABLE_NON_RECTANGULAR == aSplitType || // normal blocks
NS_FRAME_NOT_SPLITTABLE == aSplitType || // things like images mapped to display: block
PR_TRUE == treatAsNotSplittable) // text input controls mapped to display: block (special case)
{
if (mBand.GetFloaterCount()) {
// Use the float-edge property to determine how the child block
// will interact with the floater.
const nsStyleBorder* borderStyle;
aFrame->GetStyleData(eStyleStruct_Border,
(const nsStyleStruct*&) borderStyle);
switch (borderStyle->mFloatEdge) {
default:
case NS_STYLE_FLOAT_EDGE_CONTENT: // content and only content does runaround of floaters
// The child block will flow around the floater. Therefore
// give it all of the available space.
aResult.x = borderPadding.left;
aResult.width = GetFlag(BRS_UNCONSTRAINEDWIDTH)
? NS_UNCONSTRAINEDSIZE
: mContentArea.width;
break;
case NS_STYLE_FLOAT_EDGE_BORDER:
case NS_STYLE_FLOAT_EDGE_PADDING:
{
// The child block's border should be placed adjacent to,
// but not overlap the floater(s).
nsMargin m(0, 0, 0, 0);
const nsStyleMargin* styleMargin;
aFrame->GetStyleData(eStyleStruct_Margin,
(const nsStyleStruct*&) styleMargin);
styleMargin->GetMargin(m); // XXX percentage margins
if (NS_STYLE_FLOAT_EDGE_PADDING == borderStyle->mFloatEdge) {
// Add in border too
nsMargin b;
borderStyle->GetBorder(b);
m += b;
}
// determine left edge
if (mBand.GetLeftFloaterCount()) {
aResult.x = mAvailSpaceRect.x + borderPadding.left - m.left;
}
else {
aResult.x = borderPadding.left;
}
// determine width
if (GetFlag(BRS_UNCONSTRAINEDWIDTH)) {
aResult.width = NS_UNCONSTRAINEDSIZE;
}
else {
if (mBand.GetRightFloaterCount()) {
if (mBand.GetLeftFloaterCount()) {
aResult.width = mAvailSpaceRect.width + m.left + m.right;
}
else {
aResult.width = mAvailSpaceRect.width + m.right;
}
}
else {
aResult.width = mAvailSpaceRect.width + m.left;
}
}
}
break;
case NS_STYLE_FLOAT_EDGE_MARGIN:
{
// The child block's margins should be placed adjacent to,
// but not overlap the floater.
aResult.x = mAvailSpaceRect.x + borderPadding.left;
aResult.width = mAvailSpaceRect.width;
}
break;
}
}
else {
// Since there are no floaters present the float-edge property
// doesn't matter therefore give the block element all of the
// available space since it will flow around the floater itself.
aResult.x = borderPadding.left;
aResult.width = GetFlag(BRS_UNCONSTRAINEDWIDTH)
? NS_UNCONSTRAINEDSIZE
: mContentArea.width;
}
}
else {
// The frame is clueless about the space manager and therefore we
// only give it free space. An example is a table frame - the
// tables do not flow around floaters.
aResult.x = mAvailSpaceRect.x + borderPadding.left;
aResult.width = mAvailSpaceRect.width;
}
#ifdef REALLY_NOISY_REFLOW
printf(" CBAS: result %d %d %d %d\n", aResult.x, aResult.y, aResult.width, aResult.height);
#endif
}
void
nsBlockReflowState::GetAvailableSpace(nscoord aY)
{
#ifdef DEBUG
// Verify that the caller setup the coordinate system properly
nscoord wx, wy;
mSpaceManager->GetTranslation(wx, wy);
NS_ASSERTION((wx == mSpaceManagerX) && (wy == mSpaceManagerY),
"bad coord system");
#endif
mBand.GetAvailableSpace(aY - BorderPadding().top, mAvailSpaceRect);
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n",
mAvailSpaceRect.x, mAvailSpaceRect.y,
mAvailSpaceRect.width, mAvailSpaceRect.height,
mBand.GetTrapezoidCount());
}
#endif
}
PRBool
nsBlockReflowState::ClearPastFloaters(PRUint8 aBreakType)
{
nscoord saveY, deltaY;
PRBool applyTopMargin = PR_FALSE;
switch (aBreakType) {
case NS_STYLE_CLEAR_LEFT:
case NS_STYLE_CLEAR_RIGHT:
case NS_STYLE_CLEAR_LEFT_AND_RIGHT:
// Apply the previous margin before clearing
saveY = mY + mPrevBottomMargin.get();
ClearFloaters(saveY, aBreakType);
#ifdef NOISY_FLOATER_CLEARING
nsFrame::ListTag(stdout, mBlock);
printf(": ClearPastFloaters: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n",
mPrevBottomMargin, saveY, saveY - mPrevBottomMargin, mY,
mY - saveY);
#endif
// Determine how far we just moved. If we didn't move then there
// was nothing to clear to don't mess with the normal margin
// collapsing behavior. In either case we need to restore the Y
// coordinate to what it was before the clear.
deltaY = mY - saveY;
if (0 != deltaY) {
// Pretend that the distance we just moved is a previous
// blocks bottom margin. Note that GetAvailableSpace has been
// done so that the available space calculations will be done
// after clearing the appropriate floaters.
//
// What we are doing here is applying CSS2 section 9.5.2's
// rules for clearing - "The top margin of the generated box
// is increased enough that the top border edge is below the
// bottom outer edge of the floating boxes..."
//
// What this will do is cause the top-margin of the block
// frame we are about to reflow to be collapsed with that
// distance.
// XXXldb This doesn't handle collapsing with negative margins
// correctly, although it's arguable what "correct" is.
// XXX Are all the other margins included by this point?
mPrevBottomMargin.Zero();
mPrevBottomMargin.Include(deltaY);
mY = saveY;
// Force margin to be applied in this circumstance
applyTopMargin = PR_TRUE;
}
else {
// Put mY back to its original value since no clearing
// happened. That way the previous blocks bottom margin will
// be applied properly.
mY = saveY - mPrevBottomMargin.get();
}
break;
}
return applyTopMargin;
}
/*
* Reconstruct the vertical margin before the line |aLine| in order to
* do an incremental reflow that begins with |aLine| without reflowing
* the line before it. |aLine| may point to the fencepost at the end of
* the line list, and it is used this way since we (for now, anyway)
* always need to recover margins at the end of a block.
*
* The reconstruction involves walking backward through the line list to
* find any collapsed margins preceding the line that would have been in
* the reflow state's |mPrevBottomMargin| when we reflowed that line in
* a full reflow (under the rule in CSS2 that all adjacent vertical
* margins of blocks collapse).
*/
void
nsBlockReflowState::ReconstructMarginAbove(nsLineList::iterator aLine)
{
mPrevBottomMargin.Zero();
nsBlockFrame *block = mBlock;
const nsStyleText* styleText = NS_STATIC_CAST(const nsStyleText*,
block->mStyleContext->GetStyleData(eStyleStruct_Text));
PRBool isPre =
((NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) ||
(NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace));
nsCompatibility mode;
mPresContext->GetCompatibilityMode(&mode);
PRBool isQuirkMode = mode == eCompatibility_NavQuirks;
nsLineList::iterator firstLine = block->begin_lines();
for (;;) {
--aLine;
if (aLine->IsBlock()) {
mPrevBottomMargin = aLine->GetCarriedOutBottomMargin();
break;
}
PRBool isEmpty;
aLine->IsEmpty(isQuirkMode, isPre, &isEmpty);
if (! isEmpty) {
break;
}
if (aLine == firstLine) {
// If the top margin was carried out (and thus already applied),
// set it to zero. Either way, we're done.
if ((0 == mReflowState.mComputedBorderPadding.top) &&
!(block->mState & NS_BLOCK_MARGIN_ROOT)) {
mPrevBottomMargin.Zero();
}
break;
}
}
}
void
nsBlockReflowState::RecoverStateFrom(nsLineList::iterator aLine,
nscoord aDeltaY)
{
// Make the line being recovered the current line
mCurrentLine = aLine;
// Update aState.mPrevChild as if we had reflowed all of the frames
// in this line.
// XXXldb This is expensive in some cases, since it requires walking
// |GetNextSibling|.
mPrevChild = aLine->LastChild();
// Recover mKidXMost and mMaxElementSize
nscoord xmost = aLine->mBounds.XMost();
if (xmost > mKidXMost) {
#ifdef DEBUG
if (CRAZY_WIDTH(xmost)) {
nsFrame::ListTag(stdout, mBlock);
printf(": WARNING: xmost:%d\n", xmost);
}
#endif
#ifdef NOISY_KIDXMOST
printf("%p RecoverState block %p aState.mKidXMost=%d\n", this, mBlock, xmost);
#endif
mKidXMost = xmost;
}
if (GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
#ifdef NOISY_MAX_ELEMENT_SIZE
printf("nsBlockReflowState::RecoverStateFrom block %p caching max width %d\n", mBlock, aLine->mMaxElementWidth);
#endif
UpdateMaxElementSize(nsSize(aLine->mMaxElementWidth, aLine->mBounds.height));
}
// If computing the maximum width, then update mMaximumWidth
if (GetFlag(BRS_COMPUTEMAXWIDTH)) {
#ifdef NOISY_MAXIMUM_WIDTH
printf("nsBlockReflowState::RecoverStateFrom block %p caching max width %d\n", mBlock, aLine->mMaximumWidth);
#endif
UpdateMaximumWidth(aLine->mMaximumWidth);
}
// Place floaters for this line into the space manager
if (aLine->HasFloaters()) {
// Undo border/padding translation since the nsFloaterCache's
// coordinates are relative to the frame not relative to the
// border/padding.
const nsMargin& bp = BorderPadding();
mSpaceManager->Translate(-bp.left, -bp.top);
// Place the floaters into the space-manager again. Also slide
// them, just like the regular frames on the line.
nsRect r;
nsFloaterCache* fc = aLine->GetFirstFloater();
while (fc) {
fc->mRegion.y += aDeltaY;
fc->mCombinedArea.y += aDeltaY;
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
floater->GetRect(r);
floater->MoveTo(mPresContext, r.x, r.y + aDeltaY);
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisySpaceManager) {
nscoord tx, ty;
mSpaceManager->GetTranslation(tx, ty);
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("RecoverState: txy=%d,%d (%d,%d) ",
tx, ty, mSpaceManagerX, mSpaceManagerY);
nsFrame::ListTag(stdout, floater);
printf(" r.y=%d aDeltaY=%d (sum=%d) region={%d,%d,%d,%d}\n",
r.y, aDeltaY, r.y + aDeltaY,
fc->mRegion.x, fc->mRegion.y,
fc->mRegion.width, fc->mRegion.height);
}
#endif
mSpaceManager->AddRectRegion(floater, fc->mRegion);
mLastFloaterY = fc->mRegion.y;
fc = fc->Next();
}
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow || nsBlockFrame::gNoisySpaceManager) {
mSpaceManager->List(stdout);
}
#endif
// And then put the translation back again
mSpaceManager->Translate(bp.left, bp.top);
}
}
PRBool
nsBlockReflowState::IsImpactedByFloater() const
{
#ifdef REALLY_NOISY_REFLOW
printf("nsBlockReflowState::IsImpactedByFloater %p returned %d\n",
this, mBand.GetFloaterCount());
#endif
return mBand.GetFloaterCount() > 0;
}
void
nsBlockReflowState::InitFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholder)
{
// Set the geometric parent of the floater
nsIFrame* floater = aPlaceholder->GetOutOfFlowFrame();
floater->SetParent(mBlock);
// Then add the floater to the current line and place it when
// appropriate
AddFloater(aLineLayout, aPlaceholder, PR_TRUE);
}
// This is called by the line layout's AddFloater method when a
// place-holder frame is reflowed in a line. If the floater is a
// left-most child (it's x coordinate is at the line's left margin)
// then the floater is place immediately, otherwise the floater
// placement is deferred until the line has been reflowed.
// XXXldb This behavior doesn't quite fit with CSS1 and CSS2 --
// technically we're supposed let the current line flow around the
// float as well unless it won't fit next to what we already have.
// But nobody else implements it that way...
void
nsBlockReflowState::AddFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholder,
PRBool aInitialReflow)
{
NS_PRECONDITION(mBlock->end_lines() != mCurrentLine, "null ptr");
// Allocate a nsFloaterCache for the floater
nsFloaterCache* fc = mFloaterCacheFreeList.Alloc();
fc->mPlaceholder = aPlaceholder;
fc->mIsCurrentLineFloater = aLineLayout.CanPlaceFloaterNow();
// Now place the floater immediately if possible. Otherwise stash it
// away in mPendingFloaters and place it later.
if (fc->mIsCurrentLineFloater) {
// Record this floater in the current-line list
mCurrentLineFloaters.Append(fc);
// Because we are in the middle of reflowing a placeholder frame
// within a line (and possibly nested in an inline frame or two
// that's a child of our block) we need to restore the space
// manager's translation to the space that the block resides in
// before placing the floater.
nscoord ox, oy;
mSpaceManager->GetTranslation(ox, oy);
nscoord dx = ox - mSpaceManagerX;
nscoord dy = oy - mSpaceManagerY;
mSpaceManager->Translate(-dx, -dy);
// And then place it
PRBool isLeftFloater;
FlowAndPlaceFloater(fc, &isLeftFloater);
// Pass on updated available space to the current inline reflow engine
GetAvailableSpace();
aLineLayout.UpdateBand(mAvailSpaceRect.x + BorderPadding().left, mY,
GetFlag(BRS_UNCONSTRAINEDWIDTH) ? NS_UNCONSTRAINEDSIZE : mAvailSpaceRect.width,
mAvailSpaceRect.height,
isLeftFloater,
aPlaceholder->GetOutOfFlowFrame());
// Restore coordinate system
mSpaceManager->Translate(dx, dy);
}
else {
// This floater will be placed after the line is done (it is a
// below-current-line floater).
mBelowCurrentLineFloaters.Append(fc);
}
}
void
nsBlockReflowState::UpdateMaxElementSize(const nsSize& aMaxElementSize)
{
#ifdef NOISY_MAX_ELEMENT_SIZE
nsSize oldSize = mMaxElementSize;
#endif
if (aMaxElementSize.width > mMaxElementSize.width) {
mMaxElementSize.width = aMaxElementSize.width;
}
if (aMaxElementSize.height > mMaxElementSize.height) {
mMaxElementSize.height = aMaxElementSize.height;
}
#ifdef NOISY_MAX_ELEMENT_SIZE
if ((mMaxElementSize.width != oldSize.width) ||
(mMaxElementSize.height != oldSize.height)) {
nsFrame::IndentBy(stdout, mBlock->GetDepth());
if (NS_UNCONSTRAINEDSIZE == mReflowState.availableWidth) {
printf("PASS1 ");
}
nsFrame::ListTag(stdout, mBlock);
printf(": old max-element-size=%d,%d new=%d,%d\n",
oldSize.width, oldSize.height,
mMaxElementSize.width, mMaxElementSize.height);
}
#endif
}
void
nsBlockReflowState::UpdateMaximumWidth(nscoord aMaximumWidth)
{
if (aMaximumWidth > mMaximumWidth) {
#ifdef NOISY_MAXIMUM_WIDTH
printf("nsBlockReflowState::UpdateMaximumWidth block %p caching max width %d\n", mBlock, aMaximumWidth);
#endif
mMaximumWidth = aMaximumWidth;
}
}
PRBool
nsBlockReflowState::CanPlaceFloater(const nsRect& aFloaterRect,
PRUint8 aFloats)
{
// If the current Y coordinate is not impacted by any floaters
// then by definition the floater fits.
PRBool result = PR_TRUE;
if (0 != mBand.GetFloaterCount()) {
// XXX We should allow overflow by up to half a pixel here (bug 21193).
if (mAvailSpaceRect.width < aFloaterRect.width) {
// The available width is too narrow (and its been impacted by a
// prior floater)
result = PR_FALSE;
}
else {
// At this point we know that there is enough horizontal space for
// the floater (somewhere). Lets see if there is enough vertical
// space.
if (mAvailSpaceRect.height < aFloaterRect.height) {
// The available height is too short. However, its possible that
// there is enough open space below which is not impacted by a
// floater.
//
// Compute the X coordinate for the floater based on its float
// type, assuming its placed on the current line. This is
// where the floater will be placed horizontally if it can go
// here.
nscoord xa;
if (NS_STYLE_FLOAT_LEFT == aFloats) {
xa = mAvailSpaceRect.x;
}
else {
xa = mAvailSpaceRect.XMost() - aFloaterRect.width;
// In case the floater is too big, don't go past the left edge
if (xa < mAvailSpaceRect.x) {
xa = mAvailSpaceRect.x;
}
}
nscoord xb = xa + aFloaterRect.width;
// Calculate the top and bottom y coordinates, again assuming
// that the floater is placed on the current line.
const nsMargin& borderPadding = BorderPadding();
nscoord ya = mY - borderPadding.top;
if (ya < 0) {
// CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not
// be higher than the top of its containing block." (Since the
// containing block is the content edge of the block box, this
// means the margin edge of the floater can't be higher than the
// content edge of the block that contains it.)
ya = 0;
}
nscoord yb = ya + aFloaterRect.height;
nscoord saveY = mY;
for (;;) {
// Get the available space at the new Y coordinate
mY += mAvailSpaceRect.height;
GetAvailableSpace();
if (0 == mBand.GetFloaterCount()) {
// Winner. This band has no floaters on it, therefore
// there can be no overlap.
break;
}
// Check and make sure the floater won't intersect any
// floaters on this band. The floaters starting and ending
// coordinates must be entirely in the available space.
if ((xa < mAvailSpaceRect.x) || (xb > mAvailSpaceRect.XMost())) {
// The floater can't go here.
result = PR_FALSE;
break;
}
// See if there is now enough height for the floater.
if (yb < mY + mAvailSpaceRect.height) {
// Winner. The bottom Y coordinate of the floater is in
// this band.
break;
}
}
// Restore Y coordinate and available space information
// regardless of the outcome.
mY = saveY;
GetAvailableSpace();
}
}
}
return result;
}
void
nsBlockReflowState::FlowAndPlaceFloater(nsFloaterCache* aFloaterCache,
PRBool* aIsLeftFloater)
{
// Save away the Y coordinate before placing the floater. We will
// restore mY at the end after placing the floater. This is
// necessary because any adjustments to mY during the floater
// placement are for the floater only, not for any non-floating
// content.
nscoord saveY = mY;
// Grab the compatibility mode
nsCompatibility mode;
mPresContext->GetCompatibilityMode(&mode);
nsIFrame* floater = aFloaterCache->mPlaceholder->GetOutOfFlowFrame();
// Grab the floater's display information
const nsStyleDisplay* floaterDisplay;
floater->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&)floaterDisplay);
// This will hold the floater's geometry when we've found a place
// for it to live.
nsRect region;
// Advance mY to mLastFloaterY (if it's not past it already) to
// enforce 9.5.1 rule [2]; i.e., make sure that a float isn't
// ``above'' another float that preceded it in the flow.
mY = NS_MAX(mLastFloaterY, mY);
// See if the floater should clear any preceeding floaters...
if (NS_STYLE_CLEAR_NONE != floaterDisplay->mBreakType) {
// XXXldb Does this handle vertical margins correctly?
ClearFloaters(mY, floaterDisplay->mBreakType);
}
else {
// Get the band of available space
GetAvailableSpace();
}
// Reflow the floater
mBlock->ReflowFloater(*this, aFloaterCache->mPlaceholder, aFloaterCache->mCombinedArea,
aFloaterCache->mMargins, aFloaterCache->mOffsets);
// Get the floaters bounding box and margin information
floater->GetRect(region);
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("flowed floater: ");
nsFrame::ListTag(stdout, floater);
printf(" (%d,%d,%d,%d)\n",
region.x, region.y, region.width, region.height);
}
#endif
// Adjust the floater size by its margin. That's the area that will
// impact the space manager.
region.width += aFloaterCache->mMargins.left + aFloaterCache->mMargins.right;
region.height += aFloaterCache->mMargins.top + aFloaterCache->mMargins.bottom;
// Find a place to place the floater. The CSS2 spec doesn't want
// floaters overlapping each other or sticking out of the containing
// block if possible (CSS2 spec section 9.5.1, see the rule list).
NS_ASSERTION((NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) ||
(NS_STYLE_FLOAT_RIGHT == floaterDisplay->mFloats),
"invalid float type");
// In backwards compatibility mode, we don't bother to see if a
// floated table can ``really'' fit: in old browsers, floating
// tables are horizontally stacked regardless of available space.
// (See bug 43086 about tables vs. non-tables.)
if ((eCompatibility_NavQuirks != mode) ||
(NS_STYLE_DISPLAY_TABLE != floaterDisplay->mDisplay)) {
// Can the floater fit here?
while (! CanPlaceFloater(region, floaterDisplay->mFloats)) {
// Nope. Advance to the next band.
mY += mAvailSpaceRect.height;
GetAvailableSpace();
}
}
// Assign an x and y coordinate to the floater. Note that the x,y
// coordinates are computed <b>relative to the translation in the
// spacemanager</b> which means that the impacted region will be
// <b>inside</b> the border/padding area.
PRBool okToAddRectRegion = PR_TRUE;
PRBool isLeftFloater;
if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) {
isLeftFloater = PR_TRUE;
region.x = mAvailSpaceRect.x;
}
else {
isLeftFloater = PR_FALSE;
if (NS_UNCONSTRAINEDSIZE != mAvailSpaceRect.XMost())
region.x = mAvailSpaceRect.XMost() - region.width;
else {
okToAddRectRegion = PR_FALSE;
region.x = mAvailSpaceRect.x;
}
}
*aIsLeftFloater = isLeftFloater;
const nsMargin& borderPadding = BorderPadding();
region.y = mY - borderPadding.top;
if (region.y < 0) {
// CSS2 spec, 9.5.1 rule [4]: "A floating box's outer top may not
// be higher than the top of its containing block." (Since the
// containing block is the content edge of the block box, this
// means the margin edge of the floater can't be higher than the
// content edge of the block that contains it.)
region.y = 0;
}
// Place the floater in the space manager
if (okToAddRectRegion) {
#ifdef DEBUG
nsresult rv =
#endif
mSpaceManager->AddRectRegion(floater, region);
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "bad floater placement");
}
// Save away the floaters region in the spacemanager, after making
// it relative to the containing block's frame instead of relative
// to the spacemanager translation (which is inset by the
// border+padding).
aFloaterCache->mRegion.x = region.x + borderPadding.left;
aFloaterCache->mRegion.y = region.y + borderPadding.top;
aFloaterCache->mRegion.width = region.width;
aFloaterCache->mRegion.height = region.height;
#ifdef NOISY_SPACEMANAGER
nscoord tx, ty;
mSpaceManager->GetTranslation(tx, ty);
nsFrame::ListTag(stdout, mBlock);
printf(": PlaceFloater: AddRectRegion: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
tx, ty, mSpaceManagerX, mSpaceManagerY,
aFloaterCache->mRegion.x, aFloaterCache->mRegion.y,
aFloaterCache->mRegion.width, aFloaterCache->mRegion.height);
#endif
// Set the origin of the floater frame, in frame coordinates. These
// coordinates are <b>not</b> relative to the spacemanager
// translation, therefore we have to factor in our border/padding.
nscoord x = borderPadding.left + aFloaterCache->mMargins.left + region.x;
nscoord y = borderPadding.top + aFloaterCache->mMargins.top + region.y;
// If floater is relatively positioned, factor that in as well
// XXXldb Should this be done after handling the combined area
// below?
if (NS_STYLE_POSITION_RELATIVE == floaterDisplay->mPosition) {
x += aFloaterCache->mOffsets.left;
y += aFloaterCache->mOffsets.top;
}
// Position the floater and make sure and views are properly
// positioned. We need to explicitly position its child views as
// well, since we're moving the floater after flowing it.
floater->MoveTo(mPresContext, x, y);
nsContainerFrame::PositionFrameView(mPresContext, floater);
nsContainerFrame::PositionChildViews(mPresContext, floater);
// Update the floater combined area state
nsRect combinedArea = aFloaterCache->mCombinedArea;
combinedArea.x += x;
combinedArea.y += y;
if (!isLeftFloater &&
(GetFlag(BRS_UNCONSTRAINEDWIDTH) || GetFlag(BRS_SHRINKWRAPWIDTH))) {
// When we are placing a right floater in an unconstrained situation or
// when shrink wrapping, we don't apply it to the floater combined area
// immediately. Otherwise we end up with an infinitely wide combined
// area. Instead, we save it away in mRightFloaterCombinedArea so that
// later on when we know the width of a line we can compute a better value.
if (!mHaveRightFloaters) {
mRightFloaterCombinedArea = combinedArea;
mHaveRightFloaters = PR_TRUE;
}
else {
nsBlockFrame::CombineRects(combinedArea, mRightFloaterCombinedArea);
}
}
else {
nsBlockFrame::CombineRects(combinedArea, mFloaterCombinedArea);
}
// Remember the y-coordinate of the floater we've just placed
mLastFloaterY = mY;
// Now restore mY
mY = saveY;
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsRect r;
floater->GetRect(r);
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("placed floater: ");
nsFrame::ListTag(stdout, floater);
printf(" %d,%d,%d,%d\n", r.x, r.y, r.width, r.height);
}
#endif
}
/**
* Place below-current-line floaters.
*/
void
nsBlockReflowState::PlaceBelowCurrentLineFloaters(nsFloaterCacheList& aList)
{
nsFloaterCache* fc = aList.Head();
while (fc) {
if (!fc->mIsCurrentLineFloater) {
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("placing bcl floater: ");
nsFrame::ListTag(stdout, fc->mPlaceholder->GetOutOfFlowFrame());
printf("\n");
}
#endif
// Place the floater
PRBool isLeftFloater;
FlowAndPlaceFloater(fc, &isLeftFloater);
}
fc = fc->Next();
}
}
void
nsBlockReflowState::ClearFloaters(nscoord aY, PRUint8 aBreakType)
{
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("clear floaters: in: mY=%d aY=%d(%d)\n",
mY, aY, aY - BorderPadding().top);
}
#endif
#ifdef NOISY_FLOATER_CLEARING
printf("nsBlockReflowState::ClearFloaters: aY=%d breakType=%d\n",
aY, aBreakType);
mSpaceManager->List(stdout);
#endif
const nsMargin& bp = BorderPadding();
nscoord newY = mBand.ClearFloaters(aY - bp.top, aBreakType);
mY = newY + bp.top;
GetAvailableSpace();
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("clear floaters: out: mY=%d(%d)\n", mY, mY - bp.top);
}
#endif
}