gecko-dev/layout/generic/nsBlockReflowState.h

1517 строки
45 KiB
C

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsBlockFrame.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsIHTMLContent.h"
#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsIAnchoredItems.h"
#include "nsPlaceholderFrame.h"
#include "nsIPtr.h"
#include "nsHTMLAtoms.h"
#include "nsHTMLIIDs.h"
#include "nsHTMLValue.h"
#include "nsReflowCommand.h"
// XXX what do we do with catastrophic errors (rv < 0)? What is the
// state of the reflow world after such an error?
#undef NOISY_REFLOW
static NS_DEFINE_IID(kStyleDisplaySID, NS_STYLEDISPLAY_SID);
static NS_DEFINE_IID(kStyleSpacingSID, NS_STYLESPACING_SID);
static NS_DEFINE_IID(kIAnchoredItemsIID, NS_IANCHOREDITEMS_IID);
static NS_DEFINE_IID(kIRunaroundIID, NS_IRUNAROUND_IID);
static NS_DEFINE_IID(kIFloaterContainerIID, NS_IFLOATERCONTAINER_IID);
NS_DEF_PTR(nsIContent);
NS_DEF_PTR(nsIStyleContext);
//----------------------------------------------------------------------
void nsBlockBandData::ComputeAvailSpaceRect()
{
nsBandTrapezoid* trapezoid = data;
if (count > 1) {
// If there's more than one trapezoid that means there are floaters
PRInt32 i;
// Stop when we get to space occupied by a right floater
for (i = 0; i < count; i++) {
nsBandTrapezoid* trapezoid = &data[i];
if (trapezoid->state != nsBandTrapezoid::smAvailable) {
nsStyleDisplay* display;
// XXX Handle the case of multiple frames
trapezoid->frame->GetStyleData(kStyleDisplaySID, (nsStyleStruct*&)display);
if (NS_STYLE_FLOAT_RIGHT == display->mFloats) {
break;
}
}
}
if (i > 0) {
trapezoid = &data[i - 1];
}
}
if (nsBandTrapezoid::smAvailable == trapezoid->state) {
// The trapezoid is available
trapezoid->GetRect(availSpace);
} else {
nsStyleDisplay* display;
// The trapezoid is occupied. That means there's no available space
trapezoid->GetRect(availSpace);
// XXX Handle the case of multiple frames
trapezoid->frame->GetStyleData(kStyleDisplaySID, (nsStyleStruct*&)display);
if (NS_STYLE_FLOAT_LEFT == display->mFloats) {
availSpace.x = availSpace.XMost();
}
availSpace.width = 0;
}
}
//----------------------------------------------------------------------
nsBlockReflowState::nsBlockReflowState()
{
}
nsBlockReflowState::~nsBlockReflowState()
{
}
nsresult
nsBlockReflowState::Initialize(nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager,
const nsSize& aMaxSize,
nsSize* aMaxElementSize,
nsBlockFrame* aBlock)
{
nsresult rv = NS_OK;
mPresContext = aPresContext;
mBlock = aBlock;
mSpaceManager = aSpaceManager;
mBlockIsPseudo = aBlock->IsPseudoFrame();
mCurrentLine = nsnull;
mPrevKidFrame = nsnull;
mX = 0;
mY = 0;
mAvailSize = aMaxSize;
mUnconstrainedWidth = PRBool(mAvailSize.width == NS_UNCONSTRAINEDSIZE);
mUnconstrainedHeight = PRBool(mAvailSize.height == NS_UNCONSTRAINEDSIZE);
mMaxElementSizePointer = aMaxElementSize;
if (nsnull != aMaxElementSize) {
aMaxElementSize->width = 0;
aMaxElementSize->height = 0;
}
mKidXMost = 0;
mPrevMaxPosBottomMargin = 0;
mPrevMaxNegBottomMargin = 0;
mNextListOrdinal = -1;
mFirstChildIsInsideBullet = PR_FALSE;
return rv;
}
//----------------------------------------------------------------------
nsresult
nsBlockFrame::NewFrame(nsIFrame** aInstancePtrResult,
nsIContent* aContent,
PRInt32 aIndexInParent,
nsIFrame* aParent)
{
NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
if (nsnull == aInstancePtrResult) {
return NS_ERROR_NULL_POINTER;
}
nsIFrame* it = new nsBlockFrame(aContent, aIndexInParent, aParent);
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aInstancePtrResult = it;
return NS_OK;
}
nsBlockFrame::nsBlockFrame(nsIContent* aContent,
PRInt32 aIndexInParent,
nsIFrame* aParent)
: nsHTMLContainerFrame(aContent, aIndexInParent, aParent)
{
}
nsBlockFrame::~nsBlockFrame()
{
DestroyLines();
}
void nsBlockFrame::DestroyLines()
{
}
NS_METHOD
nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
NS_PRECONDITION(0 != aInstancePtr, "null ptr");
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(kBlockFrameCID)) {
*aInstancePtr = (void*) (this);
return NS_OK;
}
else if (aIID.Equals(kIRunaroundIID)) {
*aInstancePtr = (void*) ((nsIRunaround*) this);
return NS_OK;
}
else if (aIID.Equals(kIFloaterContainerIID)) {
*aInstancePtr = (void*) ((nsIFloaterContainer*) this);
return NS_OK;
}
return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr);
}
NS_METHOD
nsBlockFrame::IsSplittable(SplittableType& aIsSplittable) const
{
aIsSplittable = frSplittableNonRectangular;
return NS_OK;
}
NS_METHOD
nsBlockFrame::CreateContinuingFrame(nsIPresContext* aCX,
nsIFrame* aParent,
nsIFrame*& aContinuingFrame)
{
nsBlockFrame* cf = new nsBlockFrame(mContent, mIndexInParent, aParent);
PrepareContinuingFrame(aCX, aParent, cf);
aContinuingFrame = cf;
return NS_OK;
}
NS_METHOD
nsBlockFrame::ListTag(FILE* out) const
{
if ((nsnull != mGeometricParent) && IsPseudoFrame()) {
fprintf(out, "*block<");
nsIAtom* atom = mContent->GetTag();
if (nsnull != atom) {
nsAutoString tmp;
atom->ToString(tmp);
fputs(tmp, out);
}
fprintf(out, ">(%d)@%p", mIndexInParent, this);
} else {
nsHTMLContainerFrame::ListTag(out);
}
return NS_OK;
}
NS_METHOD
nsBlockFrame::List(FILE* out, PRInt32 aIndent) const
{
// Indent
for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out);
// Output the tag
ListTag(out);
// Output the first/last content offset
fprintf(out, "[%d,%d,%c] pif=%p nif=%p",
mFirstContentOffset, mLastContentOffset,
(mLastContentIsComplete ? 'T' : 'F'),
mPrevInFlow, mNextInFlow);
// Output the rect
out << mRect;
// Output the children, one line at a time
if (nsnull != mLines) {
fputs("<\n", out);
aIndent++;
nsLineData* line = mLines;
while (nsnull != line) {
line->List(out, aIndent);
line = line->mNextLine;
}
aIndent--;
for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out);
fputs(">\n", out);
} else {
fputs("<>\n", out);
}
return NS_OK;
}
NS_METHOD
nsBlockFrame::VerifyTree() const
{
nsresult rv = nsHTMLContainerFrame::VerifyTree();
if (NS_OK != rv) {
return rv;
}
rv = VerifyLines(PR_TRUE);
return rv;
}
nsresult
nsBlockFrame::VerifyLines(PRBool aFinalCheck) const
{
nsresult rv = NS_OK;
// Make sure that the list of children agrees with our child count.
// If this is not the case then the child list and the line list are
// not properly arranged.
PRInt32 len = LengthOf(mFirstChild);
NS_ASSERTION(mChildCount == len, "bad child list");
// Verify that our lines are correctly setup
PRInt32 offset = mFirstContentOffset;
PRInt32 lineChildCount = 0;
nsLineData* line = mLines;
nsLineData* prevLine = nsnull;
while (nsnull != line) {
if (aFinalCheck) {
NS_ASSERTION(((offset == line->mFirstContentOffset) &&
(line->mFirstContentOffset <= line->mLastContentOffset)),
"bad line mFirstContentOffset");
NS_ASSERTION(line->mLastContentOffset <= mLastContentOffset,
"bad line mLastContentOffset");
offset = line->mLastContentOffset;
if (line->mLastContentIsComplete) {
offset++;
}
}
lineChildCount += line->mChildCount;
rv = line->Verify(aFinalCheck);
if (NS_OK != rv) {
return rv;
}
prevLine = line;
line = line->mNextLine;
}
if (aFinalCheck && (nsnull != prevLine)) {
NS_ASSERTION(prevLine->mLastContentOffset == mLastContentOffset,
"bad mLastContentOffset");
NS_ASSERTION(prevLine->mLastContentIsComplete == mLastContentIsComplete,
"bad mLastContentIsComplete");
}
NS_ASSERTION(lineChildCount == mChildCount, "bad line counts");
return rv;
}
//----------------------------------------------------------------------
// Remove a next-in-flow from from this block's list of lines
// XXX problems here:
// 1. we always have to start from the first line: slow!
// 2. we can avoid this when the child is not the last child in a line
void
nsBlockFrame::WillDeleteNextInFlowFrame(nsIFrame* aNextInFlow)
{
// When a reflow indicates completion it's possible that
// next-in-flows were just removed. We have to remove them from any
// nsLineData's that follow the current line.
nsLineData* line = mLines;
while (nsnull != line) {
if (line->mFirstChild == aNextInFlow) {
// Remove child from line.
if (0 == --line->mChildCount) {
line->mFirstChild = nsnull;
}
else {
// Fixup the line
nsIFrame* nextKid;
aNextInFlow->GetNextSibling(nextKid);
line->mFirstChild = nextKid;
nextKid->GetIndexInParent(line->mFirstContentOffset);
}
break;
}
line = line->mNextLine;
}
}
nsresult
nsBlockFrame::ReflowInlineChild(nsIFrame* aKidFrame,
nsIPresContext* aPresContext,
nsReflowMetrics& aDesiredSize,
const nsSize& aMaxSize,
nsSize* aMaxElementSize,
ReflowStatus& aStatus)
{
aStatus = ReflowChild(aKidFrame, aPresContext, aDesiredSize, aMaxSize,
aMaxElementSize);
return NS_OK;
}
nsresult
nsBlockFrame::ReflowBlockChild(nsIFrame* aKidFrame,
nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager,
const nsSize& aMaxSize,
nsRect& aDesiredRect,
nsSize* aMaxElementSize,
ReflowStatus& aStatus)
{
aStatus = ReflowChild(aKidFrame, aPresContext, aSpaceManager,
aMaxSize, aDesiredRect, aMaxElementSize);
return NS_OK;
}
nsLineData*
nsBlockFrame::CreateLineForOverflowList(nsIFrame* aOverflowList)
{
nsLineData* newLine = new nsLineData();
if (nsnull != newLine) {
nsIFrame* kid = aOverflowList;
newLine->mFirstChild = kid;
kid->GetIndexInParent(newLine->mFirstContentOffset);
newLine->mLastContentOffset = -1;
newLine->mLastContentIsComplete = PRPackedBool(0x255);
PRInt32 kids = 0;
while (nsnull != kid) {
kids++;
kid->GetNextSibling(kid);
}
newLine->mChildCount = kids;
}
return newLine;
}
void
nsBlockFrame::DrainOverflowList()
{
nsBlockFrame* prevBlock = (nsBlockFrame*) mPrevInFlow;
if (nsnull != prevBlock) {
nsIFrame* overflowList = prevBlock->mOverflowList;
if (nsnull != overflowList) {
NS_ASSERTION(nsnull == mFirstChild, "bad overflow list");
NS_ASSERTION(nsnull == mLines, "bad overflow list");
// Create a line to hold the entire overflow list
nsLineData* newLine = CreateLineForOverflowList(overflowList);
// Place the children on our child list; this also reassigns
// their geometric parent and updates our mChildCount.
AppendChildren(overflowList);
prevBlock->mOverflowList = nsnull;
// The new line is the first line
mLines = newLine;
}
}
if (nsnull != mOverflowList) {
NS_ASSERTION(nsnull != mFirstChild,
"overflow list but no mapped children");
// Create a line to hold the overflow list
nsLineData* newLine = CreateLineForOverflowList(mOverflowList);
// Place the children on our child list; this also reassigns
// their geometric parent and updates our mChildCount.
AppendChildren(mOverflowList, PR_FALSE);
mOverflowList = nsnull;
// The new line is appended after our other lines
nsLineData* prevLine = nsnull;
nsLineData* line = mLines;
while (nsnull != line) {
prevLine = line;
line = line->mNextLine;
}
if (nsnull == prevLine) {
mLines = newLine;
}
else {
prevLine->mNextLine = newLine;
newLine->mPrevLine = prevLine;
}
}
#ifdef NS_DEBUG
VerifyLines(PR_FALSE);
#endif
}
// XXX add in code here that notices if margin's were not provided by
// the style system and when that is the case to apply the old layout
// engines margin calculations.
nsresult
nsBlockFrame::PlaceLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineData* aLine)
{
nsresult rv = NS_LINE_LAYOUT_COMPLETE;
nscoord topMargin = 0;
nscoord bottomMargin = 0;
nscoord maxNegBottomMargin = 0;
nscoord maxPosBottomMargin = 0;
// See if block margins apply to this line or not
PRBool isBlockLine = PR_FALSE;
if (1 == aLine->mChildCount) {
nsIStyleContextPtr kidSC;
nsIFrame* kid = aLine->mFirstChild;
rv = kid->GetStyleContext(aState.mPresContext, kidSC.AssignRef());
if (NS_OK != rv) return rv;
nsStyleDisplay* display = (nsStyleDisplay*)
kidSC->GetData(kStyleDisplaySID);
switch (display->mDisplay) {
case NS_STYLE_DISPLAY_BLOCK:
case NS_STYLE_DISPLAY_LIST_ITEM:
isBlockLine = PR_TRUE;
nsStyleSpacing* spacing = (nsStyleSpacing*)
kidSC->GetData(kStyleSpacingSID);
// Calculate top margin by collapsing with previous bottom margin
// if any.
nscoord maxNegTopMargin = 0;
nscoord maxPosTopMargin = 0;
if (spacing->mMargin.top < 0) {
maxNegTopMargin = -spacing->mMargin.top;
} else {
maxPosTopMargin = spacing->mMargin.top;
}
nscoord maxPos = PR_MAX(aState.mPrevMaxPosBottomMargin, maxPosTopMargin);
nscoord maxNeg = PR_MAX(aState.mPrevMaxNegBottomMargin, maxNegTopMargin);
topMargin = maxPos - maxNeg;
// Save away bottom information for later promotion into aState
if (spacing->mMargin.bottom < 0) {
maxNegBottomMargin = -spacing->mMargin.bottom;
} else {
maxPosBottomMargin = spacing->mMargin.bottom;
}
break;
}
}
// Before we move the line, make sure that it will fit in it's new
// location. It always fits if the height isn't constrained or it's
// the first line.
nscoord totalHeight = topMargin + aLine->mBounds.height;
if (!aState.mUnconstrainedHeight && (aLine != mLines)) {
if (aState.mY + totalHeight > aState.mAvailSize.height) {
// The line will not fit
rv = PushLines(aState, aLine);
goto done;
}
}
if (isBlockLine) {
if (0 != topMargin) {
// We have to move the line now that we know the top margin
// for it.
aLine->MoveLineBy(0, topMargin);
aState.mY += topMargin;
}
}
else {
// Apply previous line's bottom margin before the inline-line.
nscoord bottomMargin = aState.mPrevMaxPosBottomMargin -
aState.mPrevMaxNegBottomMargin;
if (0 != bottomMargin) {
aLine->MoveLineBy(0, bottomMargin);
aState.mY += bottomMargin;
}
}
// Consume space and advance running values
aState.mY += aLine->mBounds.height;
aState.mPrevMaxNegBottomMargin = maxNegBottomMargin;
aState.mPrevMaxPosBottomMargin = maxPosBottomMargin;
if (nsnull != aState.mMaxElementSizePointer) {
nsSize* maxSize = aState.mMaxElementSizePointer;
if (aLineLayout.mReflowData.mMaxElementSize.width > maxSize->width) {
maxSize->width = aLineLayout.mReflowData.mMaxElementSize.width;
}
if (aLineLayout.mReflowData.mMaxElementSize.height > maxSize->height) {
maxSize->height = aLineLayout.mReflowData.mMaxElementSize.height;
}
}
{
nscoord xmost = aLine->mBounds.XMost();
if (xmost > aState.mKidXMost) {
aState.mKidXMost = xmost;
}
}
// Any below current line floaters to place?
if (aState.mPendingFloaters.Count() > 0) {
PlaceBelowCurrentLineFloaters(aState, aState.mY);
// XXX Factor in the height of the floaters as well when considering
// whether the line fits.
// The default policy is that if there isn't room for the floaters then
// both the line and the floaters are pushed to the next-in-flow...
}
if (aState.mY >= aState.mCurrentBand.availSpace.YMost()) {
// The current y coordinate is now past our available space
// rectangle. Get a new band of space.
GetAvailableSpace(aState, aState.mY);
}
done:
return rv;
}
// aY has borderpadding.top already factored in
// aResult is relative to left,aY
nsresult
nsBlockFrame::GetAvailableSpace(nsBlockReflowState& aState, nscoord aY)
{
nsresult rv = NS_OK;
nsISpaceManager* sm = aState.mSpaceManager;
// Fill in band data for the specific Y coordinate
sm->Translate(aState.mBorderPadding.left, 0);
sm->GetBandData(aY, aState.mAvailSize, aState.mCurrentBand);
sm->Translate(-aState.mBorderPadding.left, 0);
// Compute the bounding rect of the available space, i.e. space
// between any left and right floaters
aState.mCurrentBand.ComputeAvailSpaceRect();
#if 0
// XXX For now we assume that there are no height restrictions
// (e.g. no "float to bottom of column/page")
nsRect& availSpace = aState.mCurrentBand.availSpace;
aResult.x = availSpace.x;
aResult.y = availSpace.y;
aResult.width = availSpace.width;
aResult.height = aState.mAvailSize.height;
#else
aState.mCurrentBand.availSpace.MoveBy(aState.mBorderPadding.left,
aState.mY);
#endif
return rv;
}
// Give aLine and any successive lines to the block's next-in-flow; if
// we don't have a next-in-flow then push all the children onto our
// overflow list.
nsresult
nsBlockFrame::PushLines(nsBlockReflowState& aState, nsLineData* aLine)
{
PRInt32 i;
// Split our child-list in two; revert our last content offset and
// completion status to the previous line.
nsLineData* prevLine = aLine->mPrevLine;
NS_PRECONDITION(nsnull != prevLine, "pushing first line");
nsIFrame* prevKidFrame = prevLine->mFirstChild;
for (i = prevLine->mChildCount - 1; --i >= 0; ) {
prevKidFrame->GetNextSibling(prevKidFrame);
}
#ifdef NS_DEBUG
nsIFrame* nextFrame;
prevKidFrame->GetNextSibling(nextFrame);
NS_ASSERTION(nextFrame == aLine->mFirstChild, "bad line list");
#endif
prevKidFrame->SetNextSibling(nsnull);
prevLine->mNextLine = nsnull;
mLastContentOffset = prevLine->mLastContentOffset;
mLastContentIsComplete = prevLine->mLastContentIsComplete;
// Push children to our next-in-flow if we have, or to our overflow list
nsBlockFrame* nextInFlow = (nsBlockFrame*) mNextInFlow;
PRInt32 pushCount = 0;
if (nsnull == nextInFlow) {
// Place children on the overflow list
mOverflowList = aLine->mFirstChild;
// Destroy the lines
nsLineData* line = aLine;
while (nsnull != line) {
pushCount += line->mChildCount;
nsLineData* next = line->mNextLine;
delete line;
line = next;
}
}
else {
aLine->mPrevLine = nsnull;
// Pass on the children to our next-in-flow
nsLineData* line = aLine;
prevLine = line;
nsIFrame* lastKid = aLine->mFirstChild;
nsIFrame* kid = lastKid;
while (nsnull != line) {
i = line->mChildCount;
pushCount += i;
NS_ASSERTION(kid == line->mFirstChild, "bad line list");
while (--i >= 0) {
kid->SetGeometricParent(nextInFlow);
nsIFrame* contentParent;
kid->GetContentParent(contentParent);
if (this == contentParent) {
kid->SetContentParent(nextInFlow);
}
lastKid = kid;
kid->GetNextSibling(kid);
}
prevLine = line;
line = line->mNextLine;
}
// Join the two line lists
nsLineData* nextInFlowLine = nextInFlow->mLines;
if (nsnull != nextInFlowLine) {
lastKid->SetNextSibling(nextInFlowLine->mFirstChild);
nextInFlowLine->mPrevLine = prevLine;
}
nsIFrame* firstKid = aLine->mFirstChild;
prevLine->mNextLine = nextInFlowLine;
nextInFlow->mLines = aLine;
nextInFlow->mFirstChild = firstKid;
nextInFlow->mChildCount += pushCount;
firstKid->GetIndexInParent(nextInFlow->mFirstContentOffset);
#ifdef NS_DEBUG
nextInFlow->VerifyLines(PR_FALSE);
#endif
}
mChildCount -= pushCount;
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
#endif
return NS_LINE_LAYOUT_NOT_COMPLETE;
}
nsresult
nsBlockFrame::ReflowMapped(nsBlockReflowState& aState)
{
nsresult rv = NS_OK;
// Get some space to start reflowing with
GetAvailableSpace(aState, aState.mY);
nsLineData* prevLine = nsnull;
nsLineData* line = mLines;
nsLineLayout lineLayout(aState);
aState.mCurrentLine = &lineLayout;
while (nsnull != line) {
// Initialize the line layout for this line
rv = lineLayout.Initialize(aState, line);
if (NS_OK != rv) {
goto done;
}
lineLayout.mPrevKidFrame = aState.mPrevKidFrame;
// Reflow the line
nsresult lineReflowStatus = lineLayout.ReflowLine();
if (lineReflowStatus < 0) {
// Some kind of hard error
rv = lineReflowStatus;
goto done;
}
mChildCount += lineLayout.mNewFrames;
// Now place it. It's possible it won't fit.
rv = PlaceLine(aState, lineLayout, line);
if (NS_LINE_LAYOUT_COMPLETE != rv) {
goto done;
}
mLastContentOffset = line->mLastContentOffset;
mLastContentIsComplete = PRBool(line->mLastContentIsComplete);
prevLine = line;
line = line->mNextLine;
aState.mPrevKidFrame = lineLayout.mPrevKidFrame;
}
done:
aState.mCurrentLine = nsnull;
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
#endif
return rv;
}
nsresult
nsBlockFrame::ReflowUnmapped(nsBlockReflowState& aState)
{
nsresult rv = NS_OK;
// If we have no children and we have a prev-in-flow then we need to
// pick up where it left off. If we have children, e.g. we're being
// resized, then our content offset will have already been set
// correctly.
nsIFrame* kidPrevInFlow = nsnull;
if ((nsnull == mFirstChild) && (nsnull != mPrevInFlow)) {
nsBlockFrame* prev = (nsBlockFrame*) mPrevInFlow;
mFirstContentOffset = prev->NextChildOffset();// XXX Is this necessary?
if (PR_FALSE == prev->mLastContentIsComplete) {
// Our prev-in-flow's last child is not complete
prev->LastChild(kidPrevInFlow);
}
}
// Get to the last line where the new content may be added
nsLineData* line = nsnull;
nsLineData* prevLine = nsnull;
if (nsnull != mLines) {
line = mLines;
while (nsnull != line->mNextLine) {
line = line->mNextLine;
}
prevLine = line->mPrevLine;
// If the last line is not complete then kidPrevInFlow should be
// set to the last-line's last child.
if (!line->mLastContentIsComplete) {
kidPrevInFlow = line->GetLastChild();
}
}
// Get some space to start reflowing with
GetAvailableSpace(aState, aState.mY);
// Now reflow the new content until we are out of new content or out
// of vertical space.
PRInt32 kidIndex = NextChildOffset();
nsLineLayout lineLayout(aState);
aState.mCurrentLine = &lineLayout;
lineLayout.mKidPrevInFlow = kidPrevInFlow;
PRInt32 contentChildCount = mContent->ChildCount();
while (kidIndex < contentChildCount) {
if (nsnull == line) {
if (!MoreToReflow(aState)) {
break;
}
line = new nsLineData();
if (nsnull == line) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto done;
}
line->mFirstContentOffset = kidIndex;
}
// Initialize the line layout for this line
rv = lineLayout.Initialize(aState, line);
if (NS_OK != rv) {
goto done;
}
lineLayout.mKidPrevInFlow = kidPrevInFlow;
lineLayout.mPrevKidFrame = aState.mPrevKidFrame;
// Reflow the line
nsresult lineReflowStatus = lineLayout.ReflowLine();
if (lineReflowStatus < 0) {
// Some kind of hard error
rv = lineReflowStatus;
goto done;
}
mChildCount += lineLayout.mNewFrames;
// Add line to the block; do this before placing the line in case
// PushLines is needed.
if (nsnull == prevLine) {
// For the first line, initialize mFirstContentOffset
mFirstContentOffset = line->mFirstContentOffset;
mFirstChild = line->mFirstChild;
mLines = line;
}
else {
prevLine->mNextLine = line;
line->mPrevLine = prevLine;
}
// Now place it. It's possible it won't fit.
rv = PlaceLine(aState, lineLayout, line);
if (NS_LINE_LAYOUT_COMPLETE != rv) {
goto done;
}
kidIndex = lineLayout.mKidIndex;
kidPrevInFlow = lineLayout.mKidPrevInFlow;
mLastContentOffset = line->mLastContentOffset;
mLastContentIsComplete = PRBool(line->mLastContentIsComplete);
prevLine = line;
line = line->mNextLine;
aState.mPrevKidFrame = lineLayout.mPrevKidFrame;
}
done:
aState.mCurrentLine = nsnull;
if (aState.mBlockIsPseudo) {
PropagateContentOffsets();
}
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
#endif
return rv;
}
nsresult
nsBlockFrame::InitializeState(nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager,
const nsSize& aMaxSize,
nsSize* aMaxElementSize,
nsBlockReflowState& aState)
{
nsresult rv;
rv = aState.Initialize(aPresContext, aSpaceManager,
aMaxSize, aMaxElementSize, this);
nsStyleSpacing* mySpacing = (nsStyleSpacing*)
mStyleContext->GetData(kStyleSpacingSID);
// Apply border and padding adjustments for regular frames only
if (!aState.mBlockIsPseudo) {
aState.mY = mySpacing->mBorderPadding.top;
aState.mX = mySpacing->mBorderPadding.left;
aState.mAvailSize.width -=
(mySpacing->mBorderPadding.left + mySpacing->mBorderPadding.right);
aState.mAvailSize.height -=
(mySpacing->mBorderPadding.top + mySpacing->mBorderPadding.bottom);
aState.mBorderPadding = mySpacing->mBorderPadding;
}
else {
aState.mBorderPadding.SizeTo(0, 0, 0, 0);
}
// Setup initial list ordinal value
nsIAtom* tag = mContent->GetTag();
if ((tag == nsHTMLAtoms::ul) || (tag == nsHTMLAtoms::ol) ||
(tag == nsHTMLAtoms::menu) || (tag == nsHTMLAtoms::dir)) {
nsHTMLValue value;
if (eContentAttr_HasValue ==
((nsIHTMLContent*)mContent)->GetAttribute(nsHTMLAtoms::start, value)) {
if (eHTMLUnit_Integer == value.GetUnit()) {
aState.mNextListOrdinal = value.GetIntValue();
}
}
}
NS_RELEASE(tag);
return rv;
}
#if XXX
NS_METHOD
nsBlockFrame::SizeTo(nscoord aWidth, nscoord aHeight)
{
printf("size to %g,%g\n", NS_TWIPS_TO_POINTS_FLOAT(aWidth),
NS_TWIPS_TO_POINTS_FLOAT(aHeight));
return nsHTMLContainerFrame::SizeTo(aWidth, aHeight);
}
NS_METHOD
nsBlockFrame::ResizeReflow(nsIPresContext* aPresContext,
nsReflowMetrics& aDesiredSize,
const nsSize& aMaxSize,
nsSize* aMaxElementSize,
ReflowStatus& aStatus)
{
nsresult rv = NS_OK;
aStatus = frComplete;
nsBlockReflowState state;
rv = InitializeState(aPresContext, aMaxSize, aMaxElementSize, state);
if (NS_OK == rv) {
nsRect desiredRect;
rv = DoResizeReflow(state, desiredRect, aStatus);
aDesiredSize.width = desiredRect.width;
aDesiredSize.height = desiredRect.height;
aDesiredSize.ascent = aDesiredSize.height;
aDesiredSize.descent = 0;
}
return rv;
}
#endif
PRBool
nsBlockFrame::MoreToReflow(nsBlockReflowState& aState)
{
PRBool rv = PR_FALSE;
#if 0
// XXX Don't need this anymore now that body has changed...
if (aState.mBlockIsPseudo) {
// Get the next content object that we would like to reflow
PRInt32 kidIndex = NextChildOffset();
nsIContentPtr kid = mContent->ChildAt(kidIndex);
if (kid.IsNotNull()) {
// Resolve style for the kid
nsIStyleContextPtr kidSC =
aState.mPresContext->ResolveStyleContextFor(kid, this);
nsStyleDisplay* kidStyleDisplay = (nsStyleDisplay*)
kidSC->GetData(kStyleDisplaySID);
switch (kidStyleDisplay->mDisplay) {
case NS_STYLE_DISPLAY_BLOCK:
case NS_STYLE_DISPLAY_LIST_ITEM:
// Block pseudo-frames do not contain other block elements
break;
default:
rv = PR_TRUE;
break;
}
}
} else {
#endif
if (NextChildOffset() < mContent->ChildCount()) {
rv = PR_TRUE;
}
#if 0
}
#endif
return rv;
}
nsBlockReflowState*
nsBlockFrame::FindBlockReflowState(nsIPresContext* aPresContext,
nsIFrame* aFrame)
{
nsBlockReflowState* state = nsnull;
if (nsnull != aFrame) {
nsIFrame* parent;
aFrame->GetGeometricParent(parent);
while (nsnull != parent) {
nsBlockFrame* block;
nsresult rv = parent->QueryInterface(kBlockFrameCID, (void**) &block);
if (NS_OK == rv) {
nsIPresShell* shell = aPresContext->GetShell();
state = (nsBlockReflowState*) shell->GetCachedData(block);
NS_RELEASE(shell);
break;
}
parent->GetGeometricParent(parent);
}
}
return state;
}
nsresult
nsBlockFrame::DoResizeReflow(nsBlockReflowState& aState,
const nsSize& aMaxSize,
nsRect& aDesiredRect,
ReflowStatus& aStatus)
{
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
PreReflowCheck();
#endif
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": Before:\n");
List(stdout, 1);
#endif
nsresult rv = NS_OK;
nsIPresShell* shell = aState.mPresContext->GetShell();
shell->PutCachedData(this, &aState);
// Check for an overflow list
DrainOverflowList();
if (nsnull != mLines) {
rv = ReflowMapped(aState);
}
if (NS_OK == rv) {
if ((nsnull != mLines) && (aState.mAvailSize.height <= 0)) {
// We are out of space
}
if (MoreToReflow(aState)) {
rv = ReflowUnmapped(aState);
}
}
// Return our desired rect
ComputeDesiredRect(aState, aMaxSize, aDesiredRect);
// Set return status
aStatus = frComplete;
if (NS_LINE_LAYOUT_NOT_COMPLETE == rv) {
rv = NS_OK;
aStatus = frNotComplete;
}
#ifdef NS_DEBUG
// Verify that the line layout code pulled everything up when it
// indicates a complete reflow.
if (frComplete == aStatus) {
nsBlockFrame* nextBlock = (nsBlockFrame*) mNextInFlow;
while (nsnull != nextBlock) {
NS_ASSERTION((nsnull == nextBlock->mLines) &&
(nextBlock->mChildCount == 0) &&
(nsnull == nextBlock->mFirstChild),
"bad completion status");
nextBlock = (nsBlockFrame*) nextBlock->mNextInFlow;
}
#if XXX
// We better not be in the same parent frame as our prev-in-flow.
// If we are it means that we were continued instead of pulling up
// children.
if (nsnull != mPrevInFlow) {
nsIFrame* ourParent = mGeometricParent;
nsIFrame* prevParent = ((nsBlockFrame*)mPrevInFlow)->mGeometricParent;
NS_ASSERTION(ourParent != prevParent, "bad continuation");
}
#endif
}
#endif
// Now that reflow has finished, remove the cached pointer
shell->RemoveCachedData(this);
NS_RELEASE(shell);
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
PostReflowCheck(aStatus);
#endif
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": After:\n");
List(stdout, 1);
#endif
return rv;
}
//----------------------------------------------------------------------
NS_METHOD
nsBlockFrame::ContentAppended(nsIPresShell* aShell,
nsIPresContext* aPresContext,
nsIContent* aContainer)
{
return nsHTMLContainerFrame::ContentAppended(aShell, aPresContext, aContainer);
}
NS_METHOD
nsBlockFrame::ContentInserted(nsIPresShell* aShell,
nsIPresContext* aPresContext,
nsIContent* aContainer,
nsIContent* aChild,
PRInt32 aIndexInParent)
{
nsresult rv = NS_OK;
return rv;
}
NS_METHOD
nsBlockFrame::ContentReplaced(nsIPresShell* aShell,
nsIPresContext* aPresContext,
nsIContent* aContainer,
nsIContent* aOldChild,
nsIContent* aNewChild,
PRInt32 aIndexInParent)
{
nsresult rv = NS_OK;
return rv;
}
NS_METHOD
nsBlockFrame::ContentDeleted(nsIPresShell* aShell,
nsIPresContext* aPresContext,
nsIContent* aContainer,
nsIContent* aChild,
PRInt32 aIndexInParent)
{
nsresult rv = NS_OK;
return rv;
}
NS_METHOD
nsBlockFrame::GetReflowMetrics(nsIPresContext* aPresContext,
nsReflowMetrics& aMetrics)
{
nsresult rv = NS_OK;
return rv;
}
//----------------------------------------------------------------------
NS_METHOD
nsBlockFrame::ResizeReflow(nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager,
const nsSize& aMaxSize,
nsRect& aDesiredRect,
nsSize* aMaxElementSize,
nsIFrame::ReflowStatus& aStatus)
{
nsresult rv = NS_OK;
aStatus = frComplete;
nsBlockReflowState state;
rv = InitializeState(aPresContext, aSpaceManager, aMaxSize,
aMaxElementSize, state);
if (NS_OK == rv) {
nsRect desiredRect;
rv = DoResizeReflow(state, aMaxSize, aDesiredRect, aStatus);
}
return rv;
}
NS_METHOD
nsBlockFrame::IncrementalReflow(nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager,
const nsSize& aMaxSize,
nsRect& aDesiredRect,
nsReflowCommand& aReflowCommand,
nsIFrame::ReflowStatus& aStatus)
{
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
PreReflowCheck();
#endif
nsresult rv = NS_OK;
aStatus = frComplete;
nsBlockReflowState state;
rv = InitializeState(aPresContext, aSpaceManager, aMaxSize,
nsnull, state);
nsIPresShell* shell = state.mPresContext->GetShell();
shell->PutCachedData(this, &state);
// Is the reflow command target at us?
if (this == aReflowCommand.GetTarget()) {
if (aReflowCommand.GetType() == nsReflowCommand::FrameAppended) {
nsLineData* lastLine = mLines;
// Get the last line
if (nsnull != lastLine) {
while (nsnull != lastLine->mNextLine) {
lastLine = lastLine->mNextLine;
}
}
// Restore the state
if ((nsnull != lastLine) && (nsnull != lastLine->mPrevLine)) {
nsLineData* prevLine = lastLine->mPrevLine;
state.mY = prevLine->mBounds.YMost();
if (!state.mUnconstrainedHeight) {
state.mAvailSize.height -= state.mY;
}
// XXX This isn't really right...
state.mKidXMost = mRect.width - state.mBorderPadding.right;
// If the previous line is a block, then factor in its bottom margin
if (prevLine->mIsBlock) {
nsStyleSpacing* spacing;
nsIFrame* kid = prevLine->mFirstChild;
kid->GetStyleData(kStyleSpacingSID, (nsStyleStruct*&)spacing);
if (spacing->mMargin.bottom < 0) {
state.mPrevMaxNegBottomMargin = -spacing->mMargin.bottom;
} else {
state.mPrevMaxPosBottomMargin = spacing->mMargin.bottom;
}
}
}
// Reflow unmapped children
rv = ReflowUnmapped(state);
// Set return status
aStatus = frComplete;
if (NS_LINE_LAYOUT_NOT_COMPLETE == rv) {
rv = NS_OK;
aStatus = frNotComplete;
}
} else {
NS_NOTYETIMPLEMENTED("unexpected reflow command");
}
} else {
NS_NOTYETIMPLEMENTED("unexpected reflow command");
}
// Return our desired rect
ComputeDesiredRect(state, aMaxSize, aDesiredRect);
// Now that reflow has finished, remove the cached pointer
shell->RemoveCachedData(this);
NS_RELEASE(shell);
#ifdef NS_DEBUG
VerifyLines(PR_TRUE);
PostReflowCheck(aStatus);
#endif
return rv;
}
void nsBlockFrame::ComputeDesiredRect(nsBlockReflowState& aState,
const nsSize& aMaxSize,
nsRect& aDesiredRect)
{
aDesiredRect.x = 0;
aDesiredRect.y = 0;
aDesiredRect.width = aState.mKidXMost + aState.mBorderPadding.right;
if (!aState.mUnconstrainedWidth) {
// Make sure we're at least as wide as the max size we were given
nscoord maxWidth = aState.mAvailSize.width + aState.mBorderPadding.left +
aState.mBorderPadding.right;
if (aDesiredRect.width < maxWidth) {
aDesiredRect.width = maxWidth;
}
}
aState.mY += aState.mBorderPadding.bottom;
nscoord lastBottomMargin = aState.mPrevMaxPosBottomMargin -
aState.mPrevMaxNegBottomMargin;
if (!aState.mUnconstrainedHeight && (lastBottomMargin > 0)) {
// It's possible that we don't have room for the last bottom
// margin (the last bottom margin is the margin following a block
// element that we contain; it isn't applied immediately because
// of the margin collapsing logic). This can happen when we are
// reflowed in a limited amount of space because we don't know in
// advance what the last bottom margin will be.
nscoord maxY = aMaxSize.height;
if (aState.mY + lastBottomMargin > maxY) {
lastBottomMargin = maxY - aState.mY;
if (lastBottomMargin < 0) {
lastBottomMargin = 0;
}
}
aState.mY += lastBottomMargin;
}
aDesiredRect.height = aState.mY;
}
//----------------------------------------------------------------------
PRBool
nsBlockFrame::AddFloater(nsIPresContext* aPresContext,
nsIFrame* aFloater,
PlaceholderFrame* aPlaceholder)
{
nsIPresShell* shell = aPresContext->GetShell();
nsBlockReflowState* state = (nsBlockReflowState*) shell->GetCachedData(this);
NS_RELEASE(shell);
if (nsnull != state) {
// Get the frame associated with the space manager, and get its
// nsIAnchoredItems interface
nsIFrame* frame = state->mSpaceManager->GetFrame();
nsIAnchoredItems* anchoredItems = nsnull;
frame->QueryInterface(kIAnchoredItemsIID, (void**)&anchoredItems);
NS_ASSERTION(nsnull != anchoredItems, "no anchored items interface");
if (nsnull != anchoredItems) {
anchoredItems->AddAnchoredItem(aFloater,
nsIAnchoredItems::anHTMLFloater,
this);
PlaceFloater(aPresContext, aFloater, aPlaceholder, *state);
return PR_TRUE;
}
}
return PR_FALSE;
}
void
nsBlockFrame::PlaceFloater(nsIPresContext* aPresContext,
nsIFrame* aFloater,
PlaceholderFrame* aPlaceholder)
{
nsIPresShell* shell = aPresContext->GetShell();
nsBlockReflowState* state = (nsBlockReflowState*) shell->GetCachedData(this);
NS_RELEASE(shell);
if (nsnull != state) {
PlaceFloater(aPresContext, aFloater, aPlaceholder, *state);
}
}
PRBool
nsBlockFrame::IsLeftMostChild(nsIFrame* aFrame)
{
do {
nsIFrame* parent;
aFrame->GetGeometricParent(parent);
// See if there are any non-zero sized child frames that precede
// aFrame in the child list
nsIFrame* child;
parent->FirstChild(child);
while ((nsnull != child) && (aFrame != child)) {
nsSize size;
// Is the child zero-sized?
child->GetSize(size);
if ((size.width > 0) || (size.height > 0)) {
// We found a non-zero sized child frame that precedes aFrame
return PR_FALSE;
}
child->GetNextSibling(child);
}
// aFrame is the left-most non-zero sized frame in its geometric parent.
// Walk up one level and check that its parent is left-most as well
aFrame = parent;
} while (aFrame != this);
return PR_TRUE;
}
void
nsBlockFrame::PlaceFloater(nsIPresContext* aPresContext,
nsIFrame* aFloater,
PlaceholderFrame* aPlaceholder,
nsBlockReflowState& aState)
{
// If the floater is the left-most non-zero size child frame then insert
// it before the current line; otherwise add it to the below-current-line
// todo list and we'll handle it when we flush out the line
if (IsLeftMostChild(aPlaceholder)) {
nsISpaceManager* sm = aState.mSpaceManager;
// Get the type of floater
nsIStyleContextPtr styleContext;
aFloater->GetStyleContext(aPresContext, styleContext.AssignRef());
nsStyleDisplay* floaterDisplay = (nsStyleDisplay*)
styleContext->GetData(kStyleDisplaySID);
// Commit some space in the space manager and adjust our current
// band of available space.
nsRect region;
aFloater->GetRect(region);
region.y = aState.mY;
if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) {
region.x = aState.mX;
} else {
NS_ASSERTION(NS_STYLE_FLOAT_RIGHT == floaterDisplay->mFloats,
"bad float type");
region.x = aState.mCurrentBand.availSpace.XMost() - region.width;
}
// XXX Don't forget the floater's margins...
sm->Translate(aState.mBorderPadding.left, 0);
sm->AddRectRegion(region, aFloater);
// Set the origin of the floater in world coordinates
nscoord worldX, worldY;
sm->GetTranslation(worldX, worldY);
aFloater->MoveTo(region.x + worldX, region.y + worldY);
sm->Translate(-aState.mBorderPadding.left, 0);
// Update the band of available space to reflect space taken up by
// the floater
GetAvailableSpace(aState, aState.mY);
if (nsnull != aState.mCurrentLine) {
nsLineLayout& lineLayout = *aState.mCurrentLine;
lineLayout.SetReflowSpace(aState.mCurrentBand.availSpace);
}
} else {
// Add the floater to our to-do list
aState.mPendingFloaters.AppendElement(aFloater);
}
}
void
nsBlockFrame::PlaceBelowCurrentLineFloaters(nsBlockReflowState& aState,
nscoord aY)
{
NS_PRECONDITION(aState.mPendingFloaters.Count() > 0, "no floaters");
nsISpaceManager* sm = aState.mSpaceManager;
nsBlockBandData* bd = &aState.mCurrentBand;
// XXX Factor this code with PlaceFloater()...
PRInt32 numFloaters = aState.mPendingFloaters.Count();
for (PRInt32 i = 0; i < numFloaters; i++) {
nsIFrame* floater = (nsIFrame*) aState.mPendingFloaters[i];
nsRect region;
// Get the band of available space
// XXX This is inefficient to do this inside the loop...
GetAvailableSpace(aState, aY);
// Get the type of floater
nsIStyleContextPtr styleContext;
floater->GetStyleContext(aState.mPresContext, styleContext.AssignRef());
nsStyleDisplay* sd = (nsStyleDisplay*)
styleContext->GetData(kStyleDisplaySID);
floater->GetRect(region);
region.y = bd->availSpace.y;
if (NS_STYLE_FLOAT_LEFT == sd->mFloats) {
region.x = bd->availSpace.x;
} else {
NS_ASSERTION(NS_STYLE_FLOAT_RIGHT == sd->mFloats, "bad float type");
region.x = bd->availSpace.XMost() - region.width;
}
// XXX Don't forget the floater's margins...
sm->Translate(aState.mBorderPadding.left, 0);
sm->AddRectRegion(region, floater);
// Set the origin of the floater in world coordinates
nscoord worldX, worldY;
sm->GetTranslation(worldX, worldY);
floater->MoveTo(region.x + worldX, region.y + worldY);
sm->Translate(-aState.mBorderPadding.left, 0);
}
aState.mPendingFloaters.Clear();
// Pass on updated available space to the current line
if (nsnull != aState.mCurrentLine) {
nsLineLayout& lineLayout = *aState.mCurrentLine;
lineLayout.SetReflowSpace(aState.mCurrentBand.availSpace);
}
}
//----------------------------------------------------------------------
nsLineData*
nsBlockFrame::GetFirstLine()
{
return mLines;
}
PRIntn
nsBlockFrame::GetSkipSides() const
{
PRIntn skip = 0;
if (nsnull != mPrevInFlow) {
skip |= 1 << NS_SIDE_TOP;
}
if (nsnull != mNextInFlow) {
skip |= 1 << NS_SIDE_BOTTOM;
}
return skip;
}