pjs/layout/generic/nsBlockReflowState.cpp

7268 строки
240 KiB
C++
Исходник Обычный вид История

1998-09-15 04:19:49 +04:00
/* -*- 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.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/
1998-09-15 04:19:49 +04:00
*
* 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.
1998-09-15 04:19:49 +04:00
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. 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>
1998-09-15 04:19:49 +04:00
*/
#include "nsCOMPtr.h"
#include "nsBlockFrame.h"
#include "nsBlockReflowContext.h"
#include "nsBlockBandData.h"
#include "nsBulletFrame.h"
#include "nsLineBox.h"
#include "nsInlineFrame.h"
1998-09-15 04:19:49 +04:00
#include "nsLineLayout.h"
#include "nsPlaceholderFrame.h"
#include "nsStyleConsts.h"
#include "nsHTMLIIDs.h"
#include "nsCSSRendering.h"
#include "nsIFrameManager.h"
1998-09-15 04:19:49 +04:00
#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsIReflowCommand.h"
#include "nsISpaceManager.h"
1998-09-15 04:19:49 +04:00
#include "nsIStyleContext.h"
#include "nsIView.h"
#include "nsIFontMetrics.h"
#include "nsHTMLParts.h"
#include "nsHTMLAtoms.h"
#include "nsHTMLValue.h"
#include "nsDOMEvent.h"
#include "nsIHTMLContent.h"
#include "prprf.h"
#include "nsLayoutAtoms.h"
#include "nsITextContent.h"
1999-07-14 21:26:20 +04:00
#include "nsStyleChangeList.h"
#include "nsISizeOfHandler.h"
#include "nsIFocusTracker.h"
#include "nsIFrameSelection.h"
#include "nsSpaceManager.h"
#include "prenv.h"
#include "plstr.h"
#include "nslog.h"
#undef fprintf
NS_IMPL_LOG(nsBlockFrameLog)
#define PRINTF NS_LOG_PRINTF(nsBlockFrameLog)
#define FLUSH NS_LOG_FLUSH(nsBlockFrameLog)
#ifdef DEBUG
static PRBool gLamePaintMetrics;
static PRBool gLameReflowMetrics;
static PRBool gNoisy;
static PRBool gNoisyDamageRepair;
static PRBool gNoisyMaxElementSize;
static PRBool gNoisyReflow;
static PRBool gReallyNoisyReflow;
static PRBool gNoisySpaceManager;
static PRBool gVerifyLines;
static PRBool gDisableResizeOpt;
struct BlockDebugFlags {
const char* name;
PRBool* on;
};
static BlockDebugFlags gFlags[] = {
{ "reflow", &gNoisyReflow },
{ "really-noisy-reflow", &gReallyNoisyReflow },
{ "max-element-size", &gNoisyMaxElementSize },
{ "space-manager", &gNoisySpaceManager },
{ "verify-lines", &gVerifyLines },
{ "damage-repair", &gNoisyDamageRepair },
{ "lame-paint-metrics", &gLamePaintMetrics },
{ "lame-reflow-metrics", &gLameReflowMetrics },
{ "disable-resize-opt", &gDisableResizeOpt },
};
#define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
static void
ShowDebugFlags()
{
PRINTF("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
BlockDebugFlags* bdf = gFlags;
BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
for (; bdf < end; bdf++) {
PRINTF(" %s\n", bdf->name);
}
PRINTF("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma seperated list of flag\n");
PRINTF("names (no whitespace)\n");
}
static void
InitDebugFlags()
{
static PRBool firstTime = PR_TRUE;
if (firstTime) {
firstTime = PR_FALSE;
char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
if (flags) {
PRBool error = PR_FALSE;
for (;;) {
char* cm = PL_strchr(flags, ',');
if (cm) *cm = '\0';
PRBool found = PR_FALSE;
BlockDebugFlags* bdf = gFlags;
BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
for (; bdf < end; bdf++) {
if (PL_strcasecmp(bdf->name, flags) == 0) {
*(bdf->on) = PR_TRUE;
PRINTF("nsBlockFrame: setting %s debug flag on\n", bdf->name);
gNoisy = PR_TRUE;
found = PR_TRUE;
break;
}
}
if (!found) {
error = PR_TRUE;
}
if (!cm) break;
*cm = ',';
flags = cm + 1;
}
if (error) {
ShowDebugFlags();
}
}
}
}
#undef NOISY_FIRST_LINE // enables debug output for first-line specific layout
#undef REALLY_NOISY_FIRST_LINE // enables extra debug output for first-line specific layout
#undef NOISY_FIRST_LETTER // enables debug output for first-letter specific layout
#undef NOISY_MAX_ELEMENT_SIZE // enables debug output for max element size computation
#undef NOISY_MAXIMUM_WIDTH // enables debug output for max width computation
#undef NOISY_KIDXMOST // enables debug output for aState.mKidXMost computation
#undef NOISY_FLOATER // enables debug output for floater reflow (the in/out metrics for the floated block)
#undef NOISY_FLOATER_CLEARING
#undef NOISY_FINAL_SIZE // enables debug output for desired width/height computation, once all children have been reflowed
#undef NOISY_REMOVE_FRAME
#undef NOISY_COMBINED_AREA // enables debug output for combined area computation
#undef NOISY_VERTICAL_MARGINS
#undef NOISY_REFLOW_REASON // gives a little info about why each reflow was requested
#undef REFLOW_STATUS_COVERAGE // I think this is most useful for printing, to see which frames return "incomplete"
#undef NOISY_SPACEMANAGER // enables debug output for space manager use, useful for analysing reflow of floaters and positioned elements
#define NOISY_BLOCK_INVALIDATE // enables debug output for all calls to invalidate
#undef REALLY_NOISY_REFLOW // some extra debug info
#endif
#define FIX_BUG_38157
#define FIX_BUG_37657
// add in a sanity check for absurdly deep frame trees. See bug 42138
// can't just use IsFrameTreeTooDeep() because that method has side effects we don't want
#define MAX_DEPTH_FOR_LIST_RENUMBERING 200 // 200 open displayable tags is pretty unrealistic
//----------------------------------------------------------------------
// Debugging support code
#ifdef DEBUG
static PRInt32 gNoiseIndent;
static const char* kReflowCommandType[] = {
"ContentChanged",
"StyleChanged",
"PullupReflow",
"PushReflow",
"CheckPullupReflow",
"ReflowDirty",
"UserDefined",
};
#endif
#ifdef REALLY_NOISY_FIRST_LINE
static void
DumpStyleGeneaology(nsIFrame* aFrame, const char* gap)
{
fputs(gap, stdout);
nsFrame::ListTag(stdout, aFrame);
PRINTF(": ");
nsIStyleContext* sc;
aFrame->GetStyleContext(&sc);
while (nsnull != sc) {
nsIStyleContext* psc;
PRINTF("%p ", sc);
psc = sc->GetParent();
NS_RELEASE(sc);
sc = psc;
}
PRINTF("\n");
}
#endif
#ifdef REFLOW_STATUS_COVERAGE
static void
RecordReflowStatus(PRBool aChildIsBlock, nsReflowStatus aFrameReflowStatus)
{
static PRUint32 record[2];
// 0: child-is-block
// 1: child-is-inline
PRIntn index = 0;
if (!aChildIsBlock) index |= 1;
// Compute new status
PRUint32 newS = record[index];
if (NS_INLINE_IS_BREAK(aFrameReflowStatus)) {
if (NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
newS |= 1;
}
else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) {
newS |= 2;
}
else {
newS |= 4;
}
}
else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) {
newS |= 8;
}
else {
newS |= 16;
}
// Log updates to the status that yield different values
if (record[index] != newS) {
record[index] = newS;
PRINTF("record(%d): %02x %02x\n", index, record[0], record[1]);
}
}
#endif
//----------------------------------------------------------------------
inline void CombineRects(const nsRect& r1, nsRect& r2)
{
nscoord xa = r2.x;
nscoord ya = r2.y;
nscoord xb = xa + r2.width;
nscoord yb = ya + r2.height;
nscoord x = r1.x;
nscoord y = r1.y;
nscoord xmost = x + r1.width;
nscoord ymost = y + r1.height;
if (x < xa) {
xa = x;
}
if (xmost > xb) {
xb = xmost;
}
if (y < ya) {
ya = y;
}
if (ymost > yb) {
yb = ymost;
}
r2.x = xa;
r2.y = ya;
r2.width = xb - xa;
r2.height = yb - ya;
}
//----------------------------------------------------------------------
class nsBlockReflowState {
public:
nsBlockReflowState(const nsHTMLReflowState& aReflowState,
nsIPresContext* aPresContext,
nsBlockFrame* aFrame,
const nsHTMLReflowMetrics& aMetrics,
PRBool aBlockMarginRoot);
~nsBlockReflowState();
/**
* Update our state when aLine is skipped over during incremental
* reflow.
*/
void RecoverStateFrom(nsLineBox* aLine, PRBool aPrevLineWasClean);
/**
* Get the available reflow space for the current y coordinate. The
* available space is relative to our coordinate system (0,0) is our
* upper left corner.
*/
void GetAvailableSpace() {
#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(mY - BorderPadding().top, mAvailSpaceRect);
#ifdef DEBUG
if (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n",
mAvailSpaceRect.x, mAvailSpaceRect.y,
mAvailSpaceRect.width, mAvailSpaceRect.height,
mBand.GetTrapezoidCount());
}
#endif
}
void 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 (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("GetAvailableSpace: band=%d,%d,%d,%d count=%d\n",
mAvailSpaceRect.x, mAvailSpaceRect.y,
mAvailSpaceRect.width, mAvailSpaceRect.height,
mBand.GetTrapezoidCount());
}
#endif
}
void InitFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholderFrame);
void AddFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholderFrame,
PRBool aInitialReflow);
PRBool CanPlaceFloater(const nsRect& aFloaterRect, PRUint8 aFloats);
void PlaceFloater(nsFloaterCache* aFloaterCache,
PRBool* aIsLeftFloater);
void PlaceBelowCurrentLineFloaters(nsFloaterCacheList& aFloaters);
void ClearFloaters(nscoord aY, PRUint8 aBreakType);
PRBool ClearPastFloaters(PRUint8 aBreakType);
PRBool IsLeftMostChild(nsIPresContext* aPresContext, nsIFrame* aFrame);
PRBool IsAdjacentWithTop() const {
return mY == mReflowState.mComputedBorderPadding.top;
}
const nsMargin& BorderPadding() const {
return mReflowState.mComputedBorderPadding;
}
const nsMargin& Margin() const {
return mReflowState.mComputedMargin;
}
void 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)) {
1999-07-14 21:26:20 +04:00
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 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;
}
}
void RecoverVerticalMargins(nsLineBox* aLine,
PRBool aApplyTopMargin,
nscoord* aTopMarginResult,
nscoord* aBottomMarginResult);
void ComputeBlockAvailSpace(nsIFrame* aFrame,
nsSplittableType aSplitType,
const nsStyleDisplay* aDisplay,
nsRect& aResult);
void RecoverStateFrom(nsLineBox* aLine,
PRBool aApplyTopMargin,
nscoord aDeltaY,
nsRect* aDamageRect);
void AdvanceToNextLine() {
mLineNumber++;
}
PRBool IsImpactedByFloater() {
#ifdef REALLY_NOISY_REFLOW
PRINTF("nsBlockReflowState::IsImpactedByFloater %p returned %d\n",
this, mBand.GetFloaterCount());
#endif
return mBand.GetFloaterCount();
}
nsLineBox* NewLineBox(nsIFrame* aFrame, PRInt32 aCount, PRBool aIsBlock);
void FreeLineBox(nsLineBox* aLine);
void StoreMaxElementSize(nsIFrame* aFloater, const nsSize& aSize) {
mBand.StoreMaxElementSize(mPresContext, aFloater, aSize);
}
//----------------------------------------
// This state is the "global" state computed once for the reflow of
// the block.
// The block frame that is using this object
nsBlockFrame* mBlock;
nsIPresContext* mPresContext;
const nsHTMLReflowState& mReflowState;
nsISpaceManager* mSpaceManager;
// The coordinates within the spacemanager where the block is being
// placed <b>after</b> taking into account the blocks border and
// padding. This, therefore, represents the inner "content area" (in
// spacemanager coordinates) where child frames will be placed,
// including child blocks and floaters.
nscoord mSpaceManagerX, mSpaceManagerY;
// XXX get rid of this
nsReflowStatus mReflowStatus;
nscoord mBottomEdge;
// The content area to reflow child frames within. The x/y
// coordinates are known to be mBorderPadding.left and
// mBorderPadding.top. The width/height may be NS_UNCONSTRAINEDSIZE
// if the container reflowing this frame has given the frame an
// unconstrained area.
nsSize mContentArea;
//----------------------------------------
// This state is "running" state updated by the reflow of each line
// in the block. This same state is "recovered" when a line is not
// dirty and is passed over during incremental reflow.
// The current line being reflowed
nsLineBox* mCurrentLine;
// The previous line just reflowed
nsLineBox* mPrevLine;
// The current Y coordinate in the block
nscoord mY;
// The available space within the current band.
nsRect mAvailSpaceRect;
// The maximum x-most of each line
nscoord mKidXMost;
// The combined area of all floaters placed so far
nsRect mFloaterCombinedArea;
// For unconstained-width reflow, we keep the right floaters
// combined area stored seperately.
PRBool mHaveRightFloaters;
nsRect mRightFloaterCombinedArea;
nsFloaterCacheFreeList mFloaterCacheFreeList;
// Previous child. This is used when pulling up a frame to update
// the sibling list.
nsIFrame* mPrevChild;
// The next immediate child frame that is the target of an
// incremental reflow command. Once that child has been reflowed we
// null this slot out.
nsIFrame* mNextRCFrame;
// The previous child frames collapsed bottom margin value.
nscoord mPrevBottomMargin;
// The current next-in-flow for the block. When lines are pulled
// from a next-in-flow, this is used to know which next-in-flow to
// pull from. When a next-in-flow is emptied of lines, we advance
// this to the next next-in-flow.
nsBlockFrame* mNextInFlow;
// The current band data for the current Y coordinate
nsBlockBandData mBand;
//----------------------------------------
// Temporary line-reflow state. This state is used during the reflow
// of a given line, but doesn't have meaning before or after.
// The list of floaters that are "current-line" floaters. These are
// added to the line after the line has been reflowed, to keep the
// list fiddling from being N^2.
nsFloaterCacheFreeList mCurrentLineFloaters;
// The list of floaters which are "below current-line"
// floaters. These are reflowed/placed after the line is reflowed
// and placed. Again, this is done to keep the list fiddling from
// being N^2.
nsFloaterCacheFreeList mBelowCurrentLineFloaters;
nsSize mMaxElementSize;
nscoord mMaximumWidth;
nscoord mMinLineHeight;
PRInt32 mLineNumber;
// block reflow state flags
#define BRS_UNCONSTRAINEDWIDTH 0x00000001
#define BRS_UNCONSTRAINEDHEIGHT 0x00000002
#define BRS_SHRINKWRAPWIDTH 0x00000004
#define BRS_NEEDRESIZEREFLOW 0x00000008
#define BRS_ISINLINEINCRREFLOW 0x00000010
#define BRS_NOWRAP 0x00000020
#define BRS_ISTOPMARGINROOT 0x00000040 // Is this frame a root for top/bottom margin collapsing?
#define BRS_ISBOTTOMMARGINROOT 0x00000080
#define BRS_APPLYTOPMARGIN 0x00000100 // See ShouldApplyTopMargin
#define BRS_COMPUTEMAXELEMENTSIZE 0x00000200
#define BRS_COMPUTEMAXWIDTH 0x00000400
#define BRS_LASTFLAG BRS_COMPUTEMAXWIDTH
PRInt16 mFlags;
void SetFlag(PRUint32 aFlag, PRBool aValue)
{
NS_ASSERTION(aFlag<=BRS_LASTFLAG, "bad flag");
NS_ASSERTION(aValue==PR_FALSE || aValue==PR_TRUE, "bad value");
if (aValue) { // set flag
mFlags |= aFlag;
}
else { // unset flag
mFlags &= ~aFlag;
}
}
PRBool GetFlag(PRUint32 aFlag) const
{
NS_ASSERTION(aFlag<=BRS_LASTFLAG, "bad flag");
PRBool result = (mFlags & aFlag);
if (result) return PR_TRUE;
return PR_FALSE;
}
};
// XXX This is vile. Make it go away
void
nsLineLayout::InitFloater(nsPlaceholderFrame* aFrame)
{
mBlockRS->InitFloater(*this, aFrame);
}
void
nsLineLayout::AddFloater(nsPlaceholderFrame* aFrame)
{
mBlockRS->AddFloater(*this, aFrame, PR_FALSE);
}
//----------------------------------------------------------------------
1998-09-15 04:19:49 +04:00
nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState,
nsIPresContext* aPresContext,
nsBlockFrame* aFrame,
const nsHTMLReflowMetrics& aMetrics,
PRBool aBlockMarginRoot)
: mBlock(aFrame),
mPresContext(aPresContext),
mReflowState(aReflowState),
mNextRCFrame(nsnull),
mPrevBottomMargin(0),
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((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
// Part 2 of a possible fix for 38157
#ifdef FIX_BUG_38157
const nsMargin& margin = Margin();
nscoord availContentWidth = aReflowState.availableWidth;
if (NS_UNCONSTRAINEDSIZE != availContentWidth) {
availContentWidth -= (borderPadding.left + borderPadding.right) +
(margin.left + margin.right);
}
mContentArea.width = PR_MIN(aReflowState.mComputedMaxWidth, availContentWidth);
#else
mContentArea.width = aReflowState.mComputedMaxWidth;
#endif
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 = nsnull;
mPrevLine = nsnull;
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,
1999-09-10 00:53:32 +04:00
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
1999-09-16 23:56:36 +04:00
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 splittable, so make a special case here
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)
{
1999-09-16 23:56:36 +04:00
if (mBand.GetFloaterCount()) {
// Use the float-edge property to determine how the child block
// will interact with the floater.
const nsStyleSpacing* spacing;
aFrame->GetStyleData(eStyleStruct_Spacing,
(const nsStyleStruct*&) spacing);
switch (spacing->mFloatEdge) {
default:
case NS_STYLE_FLOAT_EDGE_CONTENT: // content and only content does runaround of floaters
1999-09-16 23:56:36 +04:00
// 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)
1999-09-16 23:56:36 +04:00
? NS_UNCONSTRAINEDSIZE
: mContentArea.width;
break;
case NS_STYLE_FLOAT_EDGE_BORDER:
1999-09-16 23:56:36 +04:00
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);
spacing->GetMargin(m); // XXX percentage margins
if (NS_STYLE_FLOAT_EDGE_PADDING == spacing->mFloatEdge) {
// Add in border too
nsMargin b;
spacing->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)) {
1999-09-16 23:56:36 +04:00
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)
1999-09-16 23:56:36 +04:00
? NS_UNCONSTRAINEDSIZE
: mContentArea.width;
}
}
else {
1999-09-16 23:56:36 +04:00
// 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
}
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;
ClearFloaters(saveY, aBreakType);
1999-09-16 23:56:36 +04:00
#ifdef NOISY_FLOATER_CLEARING
nsFrame::ListTag(stdout, mBlock);
PRINTF(": ClearPastFloaters: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n",
1999-09-16 23:56:36 +04:00
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.
mPrevBottomMargin = deltaY;
mY = saveY;
// Force margin to be applied in this circumstance
applyTopMargin = PR_TRUE;
}
else {
// Put aState.mY back to its original value since no clearing
// happened. That way the previous blocks bottom margin will
// be applied properly.
mY = saveY - mPrevBottomMargin;
}
break;
}
return applyTopMargin;
}
// Recover the collapsed vertical margin values for aLine. Note that
// the values are not collapsed with aState.mPrevBottomMargin, nor are
// they collapsed with each other when the line height is zero.
void
nsBlockReflowState::RecoverVerticalMargins(nsLineBox* aLine,
PRBool aApplyTopMargin,
nscoord* aTopMarginResult,
nscoord* aBottomMarginResult)
{
if (aLine->IsBlock()) {
// Update band data
GetAvailableSpace();
// Setup reflow state to compute the block childs top and bottom
// margins
nsIFrame* frame = aLine->mFirstChild;
nsRect availSpaceRect;
const nsStyleDisplay* display;
frame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) display);
nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE;
frame->IsSplittable(splitType);
ComputeBlockAvailSpace(frame, splitType, display, availSpaceRect);
nsSize availSpace(availSpaceRect.width, availSpaceRect.height);
nsHTMLReflowState reflowState(mPresContext, mReflowState,
frame, availSpace);
// Compute collapsed top margin
nscoord topMargin = 0;
if (aApplyTopMargin) {
topMargin =
nsBlockReflowContext::ComputeCollapsedTopMargin(mPresContext,
reflowState);
}
// Compute collapsed bottom margin
nscoord bottomMargin = reflowState.mComputedMargin.bottom;
bottomMargin =
nsBlockReflowContext::MaxMargin(bottomMargin,
aLine->GetCarriedOutBottomMargin());
*aTopMarginResult = topMargin;
*aBottomMarginResult = bottomMargin;
}
else {
// XXX_ib
*aTopMarginResult = 0;
*aBottomMarginResult = 0;
}
}
void
nsBlockReflowState::RecoverStateFrom(nsLineBox* aLine,
PRBool aApplyTopMargin,
nscoord aDeltaY,
nsRect* aDamageRect)
{
// 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.
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);
}
// The line may have clear before semantics.
if (aLine->IsBlock() && aLine->HasBreak()) {
// Clear past floaters before the block if the clear style is not none
aApplyTopMargin = ClearPastFloaters(aLine->GetBreakType());
1999-09-16 23:56:36 +04:00
#ifdef NOISY_VERTICAL_MARGINS
nsFrame::ListTag(stdout, mBlock);
PRINTF(": RecoverStateFrom: y=%d child ", mY);
1999-09-16 23:56:36 +04:00
nsFrame::ListTag(stdout, aLine->mFirstChild);
PRINTF(" has clear of %d => %s, mPrevBottomMargin=%d\n", aLine->mBreakType,
1999-09-16 23:56:36 +04:00
aApplyTopMargin ? "applyTopMargin" : "nope", mPrevBottomMargin);
#endif
}
// Recover mPrevBottomMargin and calculate the line's new Y
// coordinate (newLineY)
nscoord newLineY = mY;
nsRect lineCombinedArea;
aLine->GetCombinedArea(&lineCombinedArea);
if (aLine->IsBlock()) {
if ((0 == aLine->mBounds.height) && (0 == lineCombinedArea.height)) {
// The line's top and bottom margin values need to be collapsed
// with the mPrevBottomMargin to determine a new
// mPrevBottomMargin value.
nscoord topMargin, bottomMargin;
RecoverVerticalMargins(aLine, aApplyTopMargin,
&topMargin, &bottomMargin);
nscoord m = nsBlockReflowContext::MaxMargin(bottomMargin,
mPrevBottomMargin);
m = nsBlockReflowContext::MaxMargin(m, topMargin);
mPrevBottomMargin = m;
}
else {
// Recover the top and bottom margins for this line
nscoord topMargin, bottomMargin;
RecoverVerticalMargins(aLine, aApplyTopMargin,
&topMargin, &bottomMargin);
// Compute the collapsed top margin value
nscoord collapsedTopMargin =
nsBlockReflowContext::MaxMargin(topMargin, mPrevBottomMargin);
// The lineY is just below the collapsed top margin value. The
// mPrevBottomMargin gets set to the bottom margin value for the
// line.
newLineY += collapsedTopMargin;
mPrevBottomMargin = bottomMargin;
}
}
else if (0 == aLine->GetHeight()) {
// For empty inline lines we leave the previous bottom margin
// alone so that it's collpased with the next line.
}
else {
// For non-empty inline lines the previous margin is applied
// before the line. Therefore apply it now and zero it out.
newLineY += mPrevBottomMargin;
mPrevBottomMargin = 0;
}
// Save away the old combined area for later
nsRect oldCombinedArea = lineCombinedArea;
// Slide the frames in the line by the computed delta. This also
// updates the lines Y coordinate and the combined area's Y
// coordinate.
nscoord finalDeltaY = newLineY - aLine->mBounds.y;
1999-09-16 23:56:36 +04:00
mBlock->SlideLine(*this, aLine, finalDeltaY);
// aLine has been slided, but...
// XXX it is not necessary to worry about the ascent of mBlock here, right?
// Indeed, depending on the status of the first line of mBlock, we can either have:
// case first line of mBlock is dirty : it will be reflowed by mBlock and so
// mBlock->mAscent will be recomputed by the block frame, and we will
// never enter into this RecoverStateFrom(aLine) function.
// case first line of mBlock is clean : it is untouched by the incremental reflow.
// In other words, aLine is never equals to mBlock->mLines in this function.
// so mBlock->mAscent will remain unchanged.
// Place floaters for this line into the space manager
if (aLine->HasFloaters()) {
1999-09-16 23:56:36 +04:00
// 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 += finalDeltaY;
fc->mCombinedArea.y += finalDeltaY;
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
floater->GetRect(r);
floater->MoveTo(mPresContext, r.x, r.y + finalDeltaY);
#ifdef DEBUG
if (gNoisyReflow || gNoisySpaceManager) {
nscoord tx, ty;
mSpaceManager->GetTranslation(tx, ty);
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("RecoverState: txy=%d,%d (%d,%d) ",
tx, ty, mSpaceManagerX, mSpaceManagerY);
nsFrame::ListTag(stdout, floater);
PRINTF(" r.y=%d finalDeltaY=%d (sum=%d) region={%d,%d,%d,%d}\n",
r.y, finalDeltaY, r.y + finalDeltaY,
fc->mRegion.x, fc->mRegion.y,
fc->mRegion.width, fc->mRegion.height);
}
#endif
mSpaceManager->AddRectRegion(floater, fc->mRegion);
fc = fc->Next();
}
#ifdef DEBUG
if (gNoisyReflow || gNoisySpaceManager) {
mSpaceManager->List(stdout);
}
#endif
1999-09-16 23:56:36 +04:00
// And then put the translation back again
mSpaceManager->Translate(bp.left, bp.top);
}
// Recover mY
mY = aLine->mBounds.YMost();
// Compute the damage area
if (aDamageRect) {
if (0 == finalDeltaY) {
aDamageRect->Empty();
} else {
aLine->GetCombinedArea(&lineCombinedArea);
aDamageRect->UnionRect(oldCombinedArea, lineCombinedArea);
}
}
// XXX Does this do anything? It doesn't seem to work.... (bug 29413)
// It's possible that the line has clear after semantics
if (!aLine->IsBlock() && aLine->HasBreak()) {
PRUint8 breakType = aLine->GetBreakType();
switch (breakType) {
case NS_STYLE_CLEAR_LEFT:
case NS_STYLE_CLEAR_RIGHT:
case NS_STYLE_CLEAR_LEFT_AND_RIGHT:
ClearFloaters(mY, breakType);
break;
}
}
}
//----------------------------------------------------------------------
const nsIID kBlockFrameCID = NS_BLOCK_FRAME_CID;
nsresult
NS_NewBlockFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRUint32 aFlags)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsBlockFrame* it = new (aPresShell) nsBlockFrame;
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
it->SetFlags(aFlags);
*aNewFrame = it;
return NS_OK;
}
nsBlockFrame::nsBlockFrame()
{
#ifdef DEBUG
InitDebugFlags();
#endif
}
nsBlockFrame::~nsBlockFrame()
{
}
NS_IMETHODIMP
nsBlockFrame::Destroy(nsIPresContext* aPresContext)
{
mAbsoluteContainer.DestroyFrames(this, aPresContext);
// Outside bullets are not in our child-list so check for them here
// and delete them when present.
if (HaveOutsideBullet()) {
1999-07-22 06:24:52 +04:00
mBullet->Destroy(aPresContext);
mBullet = nsnull;
}
mFloaters.DestroyFrames(aPresContext);
nsLineBox::DeleteLineList(aPresContext, mLines);
1999-07-22 06:24:52 +04:00
return nsBlockFrameSuper::Destroy(aPresContext);
}
NS_IMETHODIMP
nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(kBlockFrameCID)) {
nsBlockFrame* tmp = this;
*aInstancePtr = (void*) tmp;
return NS_OK;
}
if ( aIID.Equals(NS_GET_IID(nsILineIterator)) || aIID.Equals(NS_GET_IID(nsILineIteratorNavigator)) )
{
nsLineIterator* it = new nsLineIterator;
if (!it) {
*aInstancePtr = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
const nsStyleDisplay* display;
GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) display);
nsresult rv = it->Init(mLines,
display->mDirection == NS_STYLE_DIRECTION_RTL);
if (NS_FAILED(rv)) {
delete it;
return rv;
}
NS_ADDREF((nsILineIterator *) (*aInstancePtr = (void *) it));
return NS_OK;
}
return nsBlockFrameSuper::QueryInterface(aIID, aInstancePtr);
}
NS_IMETHODIMP
nsBlockFrame::IsSplittable(nsSplittableType& aIsSplittable) const
{
aIsSplittable = NS_FRAME_SPLITTABLE_NON_RECTANGULAR;
return NS_OK;
}
#ifdef DEBUG
NS_METHOD
nsBlockFrame::List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const
{
IndentBy(out, aIndent);
ListTag(out);
#ifdef DEBUG_waterson
FPRINTF(out, " [parent=%p]", mParent);
#endif
nsIView* view;
GetView(aPresContext, &view);
if (nsnull != view) {
fprintf(out, " [view=%p]", view);
}
1999-04-28 02:10:51 +04:00
if (nsnull != mNextSibling) {
fprintf(out, " next=%p", mNextSibling);
}
1998-09-15 04:19:49 +04:00
// Output the flow linkage
if (nsnull != mPrevInFlow) {
fprintf(out, " prev-in-flow=%p", mPrevInFlow);
}
if (nsnull != mNextInFlow) {
fprintf(out, " next-in-flow=%p", mNextInFlow);
}
// Output the rect and state
fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height);
if (0 != mState) {
fprintf(out, " [state=%08x]", mState);
1998-09-15 04:19:49 +04:00
}
PRInt32 numInlineLines = 0;
PRInt32 numBlockLines = 0;
if (nsnull != mLines) {
nsLineBox* line = mLines;
while (nsnull != line) {
if (line->IsBlock()) {
numBlockLines++;
}
else {
numInlineLines++;
}
line = line->mNext;
}
}
fprintf(out, " sc=%p(i=%d,b=%d)<\n", mStyleContext, numInlineLines, numBlockLines);
aIndent++;
1998-09-15 04:19:49 +04:00
// Output the lines
if (nsnull != mLines) {
nsLineBox* line = mLines;
while (nsnull != line) {
line->List(aPresContext, out, aIndent);
line = line->mNext;
1998-09-15 04:19:49 +04:00
}
}
nsIAtom* listName = nsnull;
PRInt32 listIndex = 0;
for (;;) {
nsIFrame* kid;
GetAdditionalChildListName(listIndex++, &listName);
if (nsnull == listName) {
break;
}
FirstChild(aPresContext, listName, &kid);
if (nsnull != kid) {
IndentBy(out, aIndent);
nsAutoString tmp;
if (nsnull != listName) {
listName->ToString(tmp);
fputs(tmp, out);
}
fputs("<\n", out);
while (nsnull != kid) {
nsIFrameDebug* frameDebug;
if (NS_SUCCEEDED(kid->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
frameDebug->List(aPresContext, out, aIndent + 1);
}
kid->GetNextSibling(&kid);
}
IndentBy(out, aIndent);
fputs(">\n", out);
}
NS_IF_RELEASE(listName);
}
aIndent--;
IndentBy(out, aIndent);
fputs(">\n", out);
1998-09-15 04:19:49 +04:00
return NS_OK;
}
1998-09-15 04:19:49 +04:00
NS_IMETHODIMP
nsBlockFrame::GetFrameName(nsString& aResult) const
{
return MakeFrameName("Block", aResult);
}
#endif
NS_IMETHODIMP
nsBlockFrame::GetFrameType(nsIAtom** aType) const
{
NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer");
1999-04-20 22:22:28 +04:00
*aType = nsLayoutAtoms::blockFrame;
NS_ADDREF(*aType);
return NS_OK;
}
/////////////////////////////////////////////////////////////////////////////
// Child frame enumeration
1998-09-15 04:19:49 +04:00
NS_IMETHODIMP
nsBlockFrame::FirstChild(nsIPresContext* aPresContext,
nsIAtom* aListName,
nsIFrame** aFirstChild) const
{
NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer");
if (aListName == nsLayoutAtoms::absoluteList) {
return mAbsoluteContainer.FirstChild(this, aListName, aFirstChild);
}
else if (nsnull == aListName) {
*aFirstChild = (nsnull != mLines) ? mLines->mFirstChild : nsnull;
return NS_OK;
1998-09-15 04:19:49 +04:00
}
else if (aListName == nsLayoutAtoms::overflowList) {
nsLineBox* overflowLines = GetOverflowLines(aPresContext, PR_FALSE);
*aFirstChild = overflowLines ? overflowLines->mFirstChild : nsnull;
return NS_OK;
}
else if (aListName == nsLayoutAtoms::floaterList) {
*aFirstChild = mFloaters.FirstChild();
return NS_OK;
}
else if (aListName == nsLayoutAtoms::bulletList) {
if (HaveOutsideBullet()) {
*aFirstChild = mBullet;
}
else {
*aFirstChild = nsnull;
}
return NS_OK;
}
*aFirstChild = nsnull;
return NS_ERROR_INVALID_ARG;
}
1998-09-15 04:19:49 +04:00
NS_IMETHODIMP
nsBlockFrame::GetAdditionalChildListName(PRInt32 aIndex,
nsIAtom** aListName) const
{
NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer");
if (aIndex < 0) {
return NS_ERROR_INVALID_ARG;
}
*aListName = nsnull;
switch (aIndex) {
case NS_BLOCK_FRAME_FLOATER_LIST_INDEX:
*aListName = nsLayoutAtoms::floaterList;
NS_ADDREF(*aListName);
break;
case NS_BLOCK_FRAME_BULLET_LIST_INDEX:
*aListName = nsLayoutAtoms::bulletList;
NS_ADDREF(*aListName);
break;
case NS_BLOCK_FRAME_ABSOLUTE_LIST_INDEX:
*aListName = nsLayoutAtoms::absoluteList;
NS_ADDREF(*aListName);
break;
}
return NS_OK;
}
NS_IMETHODIMP
nsBlockFrame::IsPercentageBase(PRBool& aBase) const
{
aBase = PR_TRUE;
return NS_OK;
}
1999-01-12 19:42:03 +03:00
//////////////////////////////////////////////////////////////////////
// Frame structure methods
//////////////////////////////////////////////////////////////////////
// Reflow methods
1998-09-15 04:19:49 +04:00
inline nscoord
nsBlockFrame::GetAscent() const
{
return mAscent;
}
static void
CalculateContainingBlock(const nsHTMLReflowState& aReflowState,
nscoord aFrameWidth,
nscoord aFrameHeight,
nscoord& aContainingBlockWidth,
nscoord& aContainingBlockHeight)
{
aContainingBlockWidth = -1; // have reflow state calculate
aContainingBlockHeight = -1; // have reflow state calculate
// The issue there is that for a 'height' of 'auto' the reflow state code
// won't know how to calculate the containing block height because it's
// calculated bottom up. We don't really want to do this for the initial
// containing block so that's why we have the check for if the element
// is absolutely or relatively positioned
if (aReflowState.mStylePosition->IsAbsolutelyPositioned() ||
(NS_STYLE_POSITION_RELATIVE == aReflowState.mStylePosition->mPosition)) {
aContainingBlockWidth = aFrameWidth;
aContainingBlockHeight = aFrameHeight;
// Containing block is relative to the padding edge
nsMargin border;
if (!aReflowState.mStyleSpacing->GetBorder(border)) {
NS_NOTYETIMPLEMENTED("percentage border");
}
aContainingBlockWidth -= border.left + border.right;
aContainingBlockHeight -= border.top + border.bottom;
}
}
NS_IMETHODIMP
nsBlockFrame::Reflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
DO_GLOBAL_REFLOW_COUNT("nsBlockFrame", aReflowState.reason);
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": begin reflow type %d availSize=%d,%d computedSize=%d,%d\n",
aReflowState.reason, aReflowState.availableWidth, aReflowState.availableHeight,
aReflowState.mComputedWidth, aReflowState.mComputedHeight);
}
if (gNoisy) {
gNoiseIndent++;
}
PRTime start;
PRInt32 ctc;
if (gLameReflowMetrics) {
start = PR_Now();
ctc = nsLineBox::GetCtorCount();
}
#endif
// See if it's an incremental reflow command
if (eReflowReason_Incremental == aReflowState.reason) {
// Give the absolute positioning code a chance to handle it
nscoord containingBlockWidth;
nscoord containingBlockHeight;
PRBool handled;
nsRect childBounds;
CalculateContainingBlock(aReflowState, mRect.width, mRect.height,
containingBlockWidth, containingBlockHeight);
mAbsoluteContainer.IncrementalReflow(this, aPresContext, aReflowState,
containingBlockWidth, containingBlockHeight,
handled, childBounds);
// If the incremental reflow command was handled by the absolute positioning
// code, then we're all done
if (handled) {
// Just return our current size as our desired size.
// XXX We need to know the overflow area for the flowed content, and
// we don't have a way to get that currently so for the time being pretend
// a resize reflow occured
#if 0
aMetrics.width = mRect.width;
aMetrics.height = mRect.height;
aMetrics.ascent = mRect.height;
aMetrics.descent = 0;
// Whether or not we're complete hasn't changed
aStatus = (nsnull != mNextInFlow) ? NS_FRAME_NOT_COMPLETE : NS_FRAME_COMPLETE;
#else
nsHTMLReflowState reflowState(aReflowState);
reflowState.reason = eReflowReason_Resize;
reflowState.reflowCommand = nsnull;
nsBlockFrame::Reflow(aPresContext, aMetrics, reflowState, aStatus);
#endif
// Factor the absolutely positioned child bounds into the overflow area
aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea, childBounds);
// Make sure the NS_FRAME_OUTSIDE_CHILDREN flag is set correctly
if ((aMetrics.mOverflowArea.x < 0) ||
(aMetrics.mOverflowArea.y < 0) ||
(aMetrics.mOverflowArea.XMost() > aMetrics.width) ||
(aMetrics.mOverflowArea.YMost() > aMetrics.height)) {
mState |= NS_FRAME_OUTSIDE_CHILDREN;
} else {
mState &= ~NS_FRAME_OUTSIDE_CHILDREN;
}
return NS_OK;
}
}
if (IsFrameTreeTooDeep(aReflowState, aMetrics)) {
#ifdef DEBUG_kipp
{
extern char* nsPresShell_ReflowStackPointerTop;
char marker;
char* newsp = (char*) &marker;
PRINTF("XXX: frame tree is too deep; approx stack size = %d\n",
nsPresShell_ReflowStackPointerTop - newsp);
}
#endif
aStatus = NS_FRAME_COMPLETE;
return NS_OK;
}
// Should we create a space manager?
nsCOMPtr<nsISpaceManager> spaceManager;
nsISpaceManager* oldSpaceManager = aReflowState.mSpaceManager;
if (NS_BLOCK_SPACE_MGR & mState) {
nsSpaceManager* rawPtr = new nsSpaceManager(this);
if (!rawPtr) {
return NS_ERROR_OUT_OF_MEMORY;
}
spaceManager = do_QueryInterface(rawPtr);
// Set the space manager in the existing reflow state
nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState;
reflowState.mSpaceManager = spaceManager.get();
#ifdef NOISY_SPACEMANAGER
PRINTF("constructed new space manager %p\n", reflowState.mSpaceManager);
#endif
}
nsBlockReflowState state(aReflowState, aPresContext, this, aMetrics,
NS_BLOCK_MARGIN_ROOT & mState);
if (eReflowReason_Resize != aReflowState.reason) {
//RenumberLists(aPresContext);
}
nsresult rv = NS_OK;
PRBool isStyleChange = PR_FALSE;
state.SetFlag(BRS_ISINLINEINCRREFLOW, PR_FALSE);
nsIFrame* target;
switch (aReflowState.reason) {
case eReflowReason_Initial:
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": reflow=initial\n");
#endif
DrainOverflowLines(aPresContext);
rv = PrepareInitialReflow(state);
mState &= ~NS_FRAME_FIRST_REFLOW;
break;
case eReflowReason_Dirty:
break;
1998-09-15 04:19:49 +04:00
case eReflowReason_Incremental: // should call GetNext() ?
aReflowState.reflowCommand->GetTarget(target);
if (this == target) {
nsIReflowCommand::ReflowType type;
aReflowState.reflowCommand->GetType(type);
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": reflow=incremental type=%d\n", type);
#endif
switch (type) {
case nsIReflowCommand::StyleChanged:
rv = PrepareStyleChangedReflow(state);
isStyleChange = PR_TRUE;
break;
case nsIReflowCommand::ReflowDirty:
break;
default:
// Map any other incremental operations into full reflows
rv = PrepareResizeReflow(state);
break;
}
}
else {
// Get next frame in reflow command chain
aReflowState.reflowCommand->GetNext(state.mNextRCFrame);
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": reflow=incremental");
if (state.mNextRCFrame) {
PRINTF(" next=");
nsFrame::ListTag(stdout, state.mNextRCFrame);
}
PRINTF("\n");
#endif
// this code does a correct job of propogating incremental reflows (bug 25510)
// and has the potential to be very efficient. we should be able to
// terminate reflow after the incremental reflow if we can detect that
// nothing significant has changed.
PRBool isFloater;
nsLineBox* prevLine;
nsLineBox* line = FindLineFor(state.mNextRCFrame, &prevLine, &isFloater);
if (line && (PR_FALSE==line->IsBlock()))
{
2000-02-16 03:50:06 +03:00
if (!isFloater) // punt if isFloater!
{
2000-02-23 01:06:19 +03:00
// reflow the line containing the target of the incr. reflow
// first mark the line dirty and set up the state object
rv = PrepareChildIncrementalReflow(state);
state.SetFlag(BRS_ISINLINEINCRREFLOW, PR_TRUE);
2000-02-23 01:06:19 +03:00
state.mPrevLine = prevLine;
state.mCurrentLine = line;
state.mNextRCFrame = state.mNextRCFrame;
// let ReflowDirtyLines do all the work
rv = ReflowDirtyLines(state);
if (NS_FAILED(rv)) {
NS_ASSERTION(0, "Reflow failed\n");
return rv;
}
// compute the final size
ComputeFinalSize(aReflowState, state, aMetrics);
// finally, mark this block frame as having a dirty child and return
// XXX: we should be able to optimize this so we only call ReflowDirtyChild
// if it's absolutely necessary: something on the line changed size.
if (!IsIncrementalDamageConstrained(state))
{
nsCOMPtr<nsIPresShell> shell;
aPresContext->GetShell(getter_AddRefs(shell));
rv = ReflowDirtyChild(shell, state.mNextRCFrame);
//XXX: it's possible we need to do some work regarding incremental painting
// here, see code below "ReflowDirtyLines() after this switch statement.
// It might be right to factor the tail end of this method into a new method
// and call that here before calling ReflowDirtyChild().
}
2000-02-23 01:06:19 +03:00
return rv;
2000-02-16 03:50:06 +03:00
}
}
rv = PrepareChildIncrementalReflow(state);
}
break;
case eReflowReason_StyleChange:
rv = PrepareStyleChangedReflow(state);
break;
case eReflowReason_Resize:
default:
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": reflow=resize (%d)\n", aReflowState.reason);
#endif
DrainOverflowLines(aPresContext);
rv = PrepareResizeReflow(state);
break;
}
2000-02-23 01:06:19 +03:00
if (NS_FAILED(rv)) {
NS_ASSERTION(0, "setting up reflow failed.\n");
return rv;
}
// Now reflow...
rv = ReflowDirtyLines(state);
2000-02-23 01:06:19 +03:00
if (NS_FAILED(rv)) {
NS_ASSERTION(0, "reflow dirty lines failed.\n");
return rv;
}
aStatus = state.mReflowStatus;
if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
if (NS_STYLE_OVERFLOW_HIDDEN == aReflowState.mStyleDisplay->mOverflow) {
aStatus = NS_FRAME_COMPLETE;
}
else {
#ifdef DEBUG_kipp
ListTag(stdout); PRINTF(": block is not complete\n");
#endif
}
}
1998-09-15 04:19:49 +04:00
// XXX_pref get rid of this!
BuildFloaterList();
// Compute our final size
ComputeFinalSize(aReflowState, state, aMetrics);
1998-09-15 04:19:49 +04:00
if (NS_BLOCK_WRAP_SIZE & mState) {
// When the area frame is supposed to wrap around all in-flow
// children, make sure its big enough to include those that stick
// outside the box.
if (NS_FRAME_OUTSIDE_CHILDREN & mState) {
nscoord xMost = aMetrics.mOverflowArea.XMost();
if (xMost > aMetrics.width) {
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
PRINTF(": changing desired width from %d to %d\n", aMetrics.width, xMost);
#endif
aMetrics.width = xMost;
}
nscoord yMost = aMetrics.mOverflowArea.YMost();
if (yMost > aMetrics.height) {
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
PRINTF(": changing desired height from %d to %d\n", aMetrics.height, yMost);
#endif
aMetrics.height = yMost;
}
}
}
// see if verifyReflow is enabled, and if so store off the space manager pointer
#ifdef DEBUG
PRInt32 verifyReflowFlags = nsIPresShell::GetVerifyReflowFlags();
if (VERIFY_REFLOW_INCLUDE_SPACE_MANAGER & verifyReflowFlags)
{
// this is a leak of the space manager, but it's only in debug if verify reflow is enabled, so not a big deal
nsCOMPtr<nsIPresShell> shell;
aPresContext->GetShell(getter_AddRefs(shell));
if (shell) {
nsCOMPtr<nsIFrameManager> frameManager;
shell->GetFrameManager(getter_AddRefs(frameManager));
if (frameManager) {
nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState;
NS_ADDREF(reflowState.mSpaceManager);
rv = frameManager->SetFrameProperty(this, nsLayoutAtoms::spaceManagerProperty,
reflowState.mSpaceManager, nsnull /* should be nsSpaceManagerDestroyer*/);
}
}
}
#endif
// If we set the space manager, then restore the old space manager now that we're
// going out of scope
if (NS_BLOCK_SPACE_MGR & mState) {
nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState;
#ifdef NOISY_SPACEMANAGER
PRINTF("restoring old space manager %p\n", oldSpaceManager);
#endif
reflowState.mSpaceManager = oldSpaceManager;
}
#ifdef NOISY_SPACEMANAGER
nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState;
if (reflowState.mSpaceManager) {
ListTag(stdout);
PRINTF(": space-manager %p after reflow\n", reflowState.mSpaceManager);
reflowState.mSpaceManager->List(stdout);
}
#endif
// If this is an incremental reflow and we changed size, then make sure our
// border is repainted if necessary
if (eReflowReason_Incremental == aReflowState.reason ||
eReflowReason_Dirty == aReflowState.reason) {
if (isStyleChange) {
// Lots of things could have changed so damage our entire
// bounds
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 1 (%d, %d, %d, %d)\n",
this, 0, 0, mRect.width, mRect.height);
#endif
Invalidate(aPresContext, nsRect(0, 0, mRect.width, mRect.height));
} else {
nsMargin border = aReflowState.mComputedBorderPadding -
aReflowState.mComputedPadding;
// See if our width changed
if ((aMetrics.width != mRect.width) && (border.right > 0)) {
nsRect damageRect;
if (aMetrics.width < mRect.width) {
// Our new width is smaller, so we need to make sure that
// we paint our border in its new position
damageRect.x = aMetrics.width - border.right;
damageRect.width = border.right;
damageRect.y = 0;
damageRect.height = aMetrics.height;
} else {
// Our new width is larger, so we need to erase our border in its
// old position
damageRect.x = mRect.width - border.right;
damageRect.width = border.right;
damageRect.y = 0;
damageRect.height = mRect.height;
}
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 2 (%d, %d, %d, %d)\n",
this, damageRect.x, damageRect.y, damageRect.width, damageRect.height);
#endif
Invalidate(aPresContext, damageRect);
}
// See if our height changed
if ((aMetrics.height != mRect.height) && (border.bottom > 0)) {
nsRect damageRect;
if (aMetrics.height < mRect.height) {
// Our new height is smaller, so we need to make sure that
// we paint our border in its new position
damageRect.x = 0;
damageRect.width = aMetrics.width;
damageRect.y = aMetrics.height - border.bottom;
damageRect.height = border.bottom;
} else {
// Our new height is larger, so we need to erase our border in its
// old position
damageRect.x = 0;
damageRect.width = mRect.width;
damageRect.y = mRect.height - border.bottom;
damageRect.height = border.bottom;
}
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 3 (%d, %d, %d, %d)\n",
this, damageRect.x, damageRect.y, damageRect.width, damageRect.height);
#endif
Invalidate(aPresContext, damageRect);
}
}
}
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed, e.g., elements with a percentage
// based width/height
if (NS_SUCCEEDED(rv) && mAbsoluteContainer.HasAbsoluteFrames()) {
nscoord containingBlockWidth;
nscoord containingBlockHeight;
nsRect childBounds;
CalculateContainingBlock(aReflowState, aMetrics.width, aMetrics.height,
containingBlockWidth, containingBlockHeight);
rv = mAbsoluteContainer.Reflow(this, aPresContext, aReflowState,
containingBlockWidth, containingBlockHeight,
childBounds);
// Factor the absolutely positioned child bounds into the overflow area
aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea, childBounds);
// Make sure the NS_FRAME_OUTSIDE_CHILDREN flag is set correctly
if ((aMetrics.mOverflowArea.x < 0) ||
(aMetrics.mOverflowArea.y < 0) ||
(aMetrics.mOverflowArea.XMost() > aMetrics.width) ||
(aMetrics.mOverflowArea.YMost() > aMetrics.height)) {
mState |= NS_FRAME_OUTSIDE_CHILDREN;
} else {
mState &= ~NS_FRAME_OUTSIDE_CHILDREN;
}
}
#ifdef DEBUG
if (gNoisy) {
gNoiseIndent--;
}
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": status=%x (%scomplete) metrics=%d,%d carriedMargin=%d",
aStatus, NS_FRAME_IS_COMPLETE(aStatus) ? "" : "not ",
aMetrics.width, aMetrics.height,
aMetrics.mCarriedOutBottomMargin);
if (mState & NS_FRAME_OUTSIDE_CHILDREN) {
PRINTF(" combinedArea={%d,%d,%d,%d}",
aMetrics.mOverflowArea.x,
aMetrics.mOverflowArea.y,
aMetrics.mOverflowArea.width,
aMetrics.mOverflowArea.height);
}
if (aMetrics.maxElementSize) {
PRINTF(" maxElementSize=%d,%d",
aMetrics.maxElementSize->width,
aMetrics.maxElementSize->height);
}
PRINTF("\n");
}
if (gLameReflowMetrics) {
PRTime end = PR_Now();
PRInt32 ectc = nsLineBox::GetCtorCount();
PRInt32 numLines = nsLineBox::ListLength(mLines);
if (!numLines) numLines = 1;
PRTime delta, perLineDelta, lines;
LL_I2L(lines, numLines);
LL_SUB(delta, end, start);
LL_DIV(perLineDelta, delta, lines);
ListTag(stdout);
char buf[400];
PR_snprintf(buf, sizeof(buf),
": %lld elapsed (%lld per line) (%d lines; %d new lines)",
delta, perLineDelta, numLines, ectc - ctc);
PRINTF("%s\n", buf);
}
#endif
#ifdef NOISY_MAX_ELEMENT_SIZE
if (aMetrics.maxElementSize) {
PRINTF("block %p returning with maxElementSize=%d,%d\n", this,
aMetrics.maxElementSize->width,
aMetrics.maxElementSize->height);
}
#endif
return rv;
1998-09-15 04:19:49 +04:00
}
static PRBool
HaveAutoWidth(const nsHTMLReflowState& aReflowState)
{
1999-09-10 00:53:32 +04:00
const nsHTMLReflowState* rs = &aReflowState;
if (NS_UNCONSTRAINEDSIZE == rs->mComputedWidth) {
return PR_TRUE;
}
1999-09-10 00:53:32 +04:00
const nsStylePosition* pos = rs->mStylePosition;
for (;;) {
1999-09-10 00:53:32 +04:00
if (!pos) {
return PR_TRUE;
}
nsStyleUnit widthUnit = pos->mWidth.GetUnit();
if (eStyleUnit_Auto == widthUnit) {
return PR_TRUE;
}
if (eStyleUnit_Inherit != widthUnit) {
break;
}
const nsHTMLReflowState* prs = (const nsHTMLReflowState*)
1999-09-10 00:53:32 +04:00
rs->parentReflowState;
if (!prs) {
return PR_TRUE;
}
1999-09-10 00:53:32 +04:00
rs = prs;
pos = prs->mStylePosition;
}
1999-09-10 00:53:32 +04:00
return PR_FALSE;
}
static PRBool
IsPercentageAwareChild(const nsIFrame* aFrame)
{
const nsStyleSpacing* space;
nsresult rv = aFrame->GetStyleData(eStyleStruct_Spacing,(const nsStyleStruct*&) space);
if (NS_FAILED(rv)) {
return PR_TRUE; // just to be on the safe side
}
if (nsLineLayout::IsPercentageUnitSides(&space->mMargin)
|| nsLineLayout::IsPercentageUnitSides(&space->mPadding)
|| nsLineLayout::IsPercentageUnitSides(&space->mBorderRadius)) {
return PR_TRUE;
}
const nsStylePosition* pos;
rv = aFrame->GetStyleData(eStyleStruct_Position,(const nsStyleStruct*&) pos);
if (NS_FAILED(rv)) {
return PR_TRUE; // just to be on the safe side
}
if (eStyleUnit_Percent == pos->mWidth.GetUnit()
|| eStyleUnit_Percent == pos->mMaxWidth.GetUnit()
|| eStyleUnit_Percent == pos->mMinWidth.GetUnit()
|| eStyleUnit_Percent == pos->mHeight.GetUnit()
|| eStyleUnit_Percent == pos->mMinHeight.GetUnit()
|| eStyleUnit_Percent == pos->mMaxHeight.GetUnit()
|| nsLineLayout::IsPercentageUnitSides(&pos->mOffset)) { // XXX need more here!!!
return PR_TRUE;
}
return PR_FALSE;
}
void
nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState,
nsBlockReflowState& aState,
nsHTMLReflowMetrics& aMetrics)
1998-09-15 04:19:49 +04:00
{
const nsMargin& borderPadding = aState.BorderPadding();
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
PRINTF(": mY=%d mIsBottomMarginRoot=%s mPrevBottomMargin=%d bp=%d,%d\n",
aState.mY, aState.GetFlag(BRS_ISBOTTOMMARGINROOT) ? "yes" : "no",
aState.mPrevBottomMargin,
borderPadding.top, borderPadding.bottom);
#endif
// Compute final width
nscoord maxWidth = 0, maxHeight = 0;
#ifdef NOISY_KIDXMOST
PRINTF("%p aState.mKidXMost=%d\n", this, aState.mKidXMost);
#endif
nscoord minWidth = aState.mKidXMost + borderPadding.right;
if (!HaveAutoWidth(aReflowState)) {
// Use style defined width
aMetrics.width = borderPadding.left + aReflowState.mComputedWidth +
borderPadding.right;
// XXX quote css1 section here
if ((0 == aReflowState.mComputedWidth) && (aMetrics.width < minWidth)) {
aMetrics.width = minWidth;
}
// When style defines the width use it for the max-element-size
// because we can't shrink any smaller.
maxWidth = aMetrics.width;
1998-09-15 04:19:49 +04:00
}
else {
nscoord computedWidth = minWidth;
PRBool compact = PR_FALSE;
#if 0
if (NS_STYLE_DISPLAY_COMPACT == aReflowState.mStyleDisplay->mDisplay) {
// If we are display: compact AND we have no lines or we have
// exactly one line and that line is not a block line AND that
// line doesn't end in a BR of any sort THEN we remain a compact
// frame.
if ((nsnull == mLines) ||
((nsnull == mLines->mNext) && !mLines->IsBlock() &&
(NS_STYLE_CLEAR_NONE == mLines->GetBreakType())
/*XXX && (computedWidth <= aState.mCompactMarginWidth) */
)) {
compact = PR_TRUE;
}
}
#endif
// There are two options here. We either shrink wrap around our
// contents or we fluff out to the maximum block width. Note:
// We always shrink wrap when given an unconstrained width.
if ((0 == (NS_BLOCK_SHRINK_WRAP & mState)) &&
!aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) && !aState.GetFlag(BRS_SHRINKWRAPWIDTH) &&
!compact) {
// Set our width to the max width if we aren't already that
// wide. Note that the max-width has nothing to do with our
// contents (CSS2 section XXX)
computedWidth = borderPadding.left + aState.mContentArea.width +
borderPadding.right;
}
// See if we should compute our max element size
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
// Adjust the computedWidth
if (aState.GetFlag(BRS_NOWRAP)) {
// When no-wrap is true the max-element-size.width is the
// width of the widest line plus the right border. Note that
// aState.mKidXMost already has the left border factored in
//maxWidth = aState.mKidXMost + borderPadding.right;
maxWidth = aState.mMaxElementSize.width +
borderPadding.left + borderPadding.right;
}
else {
// Add in border and padding dimensions to already computed
// max-element-size values.
maxWidth = aState.mMaxElementSize.width +
borderPadding.left + borderPadding.right;
}
if (computedWidth < maxWidth) {
computedWidth = maxWidth;
}
}
// Apply min/max values
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
nscoord computedMaxWidth = aReflowState.mComputedMaxWidth +
borderPadding.left + borderPadding.right;
if (computedWidth > computedMaxWidth) {
computedWidth = computedMaxWidth;
}
}
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinWidth) {
nscoord computedMinWidth = aReflowState.mComputedMinWidth +
borderPadding.left + borderPadding.right;
if (computedWidth < computedMinWidth) {
computedWidth = computedMinWidth;
}
}
aMetrics.width = computedWidth;
// If we're shrink wrapping, then now that we know our final width we
// need to do horizontal alignment of the inline lines and make sure
// blocks are correctly sized and positioned. Any lines that need
// final adjustment will have been marked as dirty
if (aState.GetFlag(BRS_SHRINKWRAPWIDTH) && aState.GetFlag(BRS_NEEDRESIZEREFLOW)) {
// If the parent reflow state is also shrink wrap width, then
// we don't need to do this, because it will reflow us after it
// calculates the final width
PRBool parentIsShrinkWrapWidth = PR_FALSE;
if (aReflowState.parentReflowState) {
if (NS_SHRINKWRAPWIDTH == aReflowState.parentReflowState->mComputedWidth) {
parentIsShrinkWrapWidth = PR_TRUE;
}
}
1998-09-15 04:19:49 +04:00
if (!parentIsShrinkWrapWidth) {
nsHTMLReflowState reflowState(aReflowState);
reflowState.mComputedWidth = aMetrics.width - borderPadding.left -
borderPadding.right;
reflowState.reason = eReflowReason_Resize;
reflowState.mSpaceManager->ClearRegions();
nscoord oldDesiredWidth = aMetrics.width;
nsBlockReflowState state(reflowState, aState.mPresContext, this, aMetrics,
NS_BLOCK_MARGIN_ROOT & mState);
ReflowDirtyLines(state);
aState.mY = state.mY;
NS_ASSERTION(oldDesiredWidth == aMetrics.width, "bad desired width");
}
}
}
if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) {
PRBool parentIsShrinkWrapWidth = PR_FALSE;
if (aReflowState.parentReflowState) {
if (NS_SHRINKWRAPWIDTH == aReflowState.parentReflowState->mComputedWidth) {
parentIsShrinkWrapWidth = PR_TRUE;
}
}
}
// Compute final height
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedHeight) {
// Use style defined height
aMetrics.height = borderPadding.top + aReflowState.mComputedHeight +
borderPadding.bottom;
// When style defines the height use it for the max-element-size
// because we can't shrink any smaller.
maxHeight = aMetrics.height;
// Don't carry out a bottom margin when our height is fixed
// unless the bottom of the last line adjoins the bottom of our
// content area.
if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) {
if (aState.mY + aState.mPrevBottomMargin != aMetrics.height) {
aState.mPrevBottomMargin = 0;
}
}
}
else {
nscoord autoHeight = aState.mY;
// Shrink wrap our height around our contents.
if (aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) {
// When we are a bottom-margin root make sure that our last
// childs bottom margin is fully applied.
// XXX check for a fit
autoHeight += aState.mPrevBottomMargin;
}
autoHeight += borderPadding.bottom;
// Apply min/max values
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxHeight) {
nscoord computedMaxHeight = aReflowState.mComputedMaxHeight +
borderPadding.top + borderPadding.bottom;
if (autoHeight > computedMaxHeight) {
autoHeight = computedMaxHeight;
}
}
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMinHeight) {
nscoord computedMinHeight = aReflowState.mComputedMinHeight +
borderPadding.top + borderPadding.bottom;
if (autoHeight < computedMinHeight) {
autoHeight = computedMinHeight;
}
}
aMetrics.height = autoHeight;
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
maxHeight = aState.mMaxElementSize.height +
borderPadding.top + borderPadding.bottom;
}
}
// Set our desired ascent and descent.
// We need to check for special cases where mAscent is not yet properly set.
// There are two cases to consider: when the first line is a block, or
// when the first line is empty and is followed by a second line that
// is a block (e.g., <td>\n<div>). We need to fetch the ascent of the
// first child of the first non-empty line.
nsLineBox* line = mLines;
// see if this first line is empty. if so, move on to the second line
if (line && (0 == line->GetHeight())) {
line = line->mNext;
}
// see if the line contains a block. if so, fetch the ascent of the block
if (line && line->mFirstChild && line->IsBlock()) {
nsBlockFrame* bf;
nsresult res = line->mFirstChild->QueryInterface(kBlockFrameCID, (void**)&bf);
if (NS_SUCCEEDED(res) && bf) {
mAscent = bf->GetAscent();
}
}
aMetrics.ascent = mAscent;
aMetrics.descent = aMetrics.height - aMetrics.ascent;
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
// Store away the final value
aMetrics.maxElementSize->width = maxWidth;
aMetrics.maxElementSize->height = maxHeight;
#ifdef NOISY_MAX_ELEMENT_SIZE
PRINTF ("nsBlockFrame::CFS: %p returning MES %d\n",
this, aMetrics.maxElementSize->width);
#endif
}
// Return bottom margin information
aMetrics.mCarriedOutBottomMargin =
aState.GetFlag(BRS_ISBOTTOMMARGINROOT) ? 0 : aState.mPrevBottomMargin;
1998-09-15 04:19:49 +04:00
#ifdef DEBUG_blocks
if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) {
ListTag(stdout);
PRINTF(": WARNING: desired:%d,%d\n", aMetrics.width, aMetrics.height);
}
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE) &&
((maxWidth > aMetrics.width) || (maxHeight > aMetrics.height))) {
ListTag(stdout);
PRINTF(": WARNING: max-element-size:%d,%d desired:%d,%d maxSize:%d,%d\n",
maxWidth, maxHeight, aMetrics.width, aMetrics.height,
aState.mReflowState.availableWidth,
aState.mReflowState.availableHeight);
}
#endif
#ifdef NOISY_MAX_ELEMENT_SIZE
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
IndentBy(stdout, GetDepth());
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
PRINTF("PASS1 ");
}
ListTag(stdout);
PRINTF(": max-element-size:%d,%d desired:%d,%d maxSize:%d,%d\n",
maxWidth, maxHeight, aMetrics.width, aMetrics.height,
aState.mReflowState.availableWidth,
aState.mReflowState.availableHeight);
}
#endif
1998-09-15 04:19:49 +04:00
// If we're requested to update our maximum width, then compute it
if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) {
// We need to add in for the right border/padding
aMetrics.mMaximumWidth = aState.mMaximumWidth + borderPadding.right;
#ifdef NOISY_MAXIMUM_WIDTH
PRINTF("nsBlockFrame::ComputeFinalSize block %p setting aMetrics.mMaximumWidth to %d\n", this, aMetrics.mMaximumWidth);
#endif
}
// Compute the combined area of our children
// XXX_perf: This can be done incrementally
nscoord xa = 0, ya = 0, xb = aMetrics.width, yb = aMetrics.height;
if (NS_STYLE_OVERFLOW_HIDDEN != aReflowState.mStyleDisplay->mOverflow) {
nsLineBox* line = mLines;
while (nsnull != line) {
// Compute min and max x/y values for the reflowed frame's
// combined areas
nsRect lineCombinedArea;
line->GetCombinedArea(&lineCombinedArea);
nscoord x = lineCombinedArea.x;
nscoord y = lineCombinedArea.y;
nscoord xmost = x + lineCombinedArea.width;
nscoord ymost = y + lineCombinedArea.height;
if (x < xa) {
xa = x;
}
if (xmost > xb) {
xb = xmost;
}
if (y < ya) {
ya = y;
}
if (ymost > yb) {
yb = ymost;
}
line = line->mNext;
}
// Factor the bullet in; normally the bullet will be factored into
// the line-box's combined area. However, if the line is a block
// line then it won't; if there are no lines, it won't. So just
// factor it in anyway (it can't hurt if it was already done).
if (mBullet) {
nsRect r;
mBullet->GetRect(r);
if (r.x < xa) xa = r.x;
if (r.y < ya) ya = r.y;
nscoord xmost = r.XMost();
if (xmost > xb) xb = xmost;
nscoord ymost = r.YMost();
if (ymost > yb) yb = ymost;
1999-03-21 00:57:03 +03:00
}
1999-07-14 21:26:20 +04:00
}
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
PRINTF(": ca=%d,%d,%d,%d\n", xa, ya, xb-xa, yb-ya);
#endif
1998-09-15 04:19:49 +04:00
// If the combined area of our children exceeds our bounding box
// then set the NS_FRAME_OUTSIDE_CHILDREN flag, otherwise clear it.
aMetrics.mOverflowArea.x = xa;
aMetrics.mOverflowArea.y = ya;
aMetrics.mOverflowArea.width = xb - xa;
aMetrics.mOverflowArea.height = yb - ya;
if ((aMetrics.mOverflowArea.x < 0) ||
(aMetrics.mOverflowArea.y < 0) ||
(aMetrics.mOverflowArea.XMost() > aMetrics.width) ||
(aMetrics.mOverflowArea.YMost() > aMetrics.height)) {
mState |= NS_FRAME_OUTSIDE_CHILDREN;
}
else {
mState &= ~NS_FRAME_OUTSIDE_CHILDREN;
}
}
1998-09-15 04:19:49 +04:00
nsresult
nsBlockFrame::PrepareInitialReflow(nsBlockReflowState& aState)
1998-09-15 04:19:49 +04:00
{
PrepareResizeReflow(aState);
return NS_OK;
}
1998-09-15 04:19:49 +04:00
nsresult
nsBlockFrame::PrepareChildIncrementalReflow(nsBlockReflowState& aState)
1998-09-15 04:19:49 +04:00
{
// Determine the line being impacted
PRBool isFloater;
nsLineBox* prevLine;
nsLineBox* line = FindLineFor(aState.mNextRCFrame, &prevLine, &isFloater);
if (nsnull == line) {
// This can't happen, but just in case it does...
return PrepareResizeReflow(aState);
}
// XXX: temporary: If the child frame is a floater then punt
if (isFloater) {
return PrepareResizeReflow(aState);
}
1998-09-15 04:19:49 +04:00
// Figure out which line to mark dirty.
MarkLineDirty(line, prevLine);
return NS_OK;
}
nsresult
nsBlockFrame::MarkLineDirty(nsLineBox* aLine, nsLineBox* aPrevLine)
{
// Mark aLine dirty
aLine->MarkDirty();
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": mark line %p dirty\n", aLine);
}
#endif
// Mark previous line dirty if its an inline line so that it can
// maybe pullup something from the line just affected.
// XXX We don't need to do this if aPrevLine ends in a break-after...
if (aPrevLine && !aPrevLine->IsBlock()) {
aPrevLine->MarkDirty();
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": mark prev-line %p dirty\n", aPrevLine);
}
#endif
}
return NS_OK;
1998-09-15 04:19:49 +04:00
}
nsresult
nsBlockFrame::UpdateBulletPosition(nsBlockReflowState& aState)
{
if (nsnull == mBullet) {
// Don't bother if there is no bullet
return NS_OK;
}
const nsStyleList* styleList;
GetStyleData(eStyleStruct_List, (const nsStyleStruct*&) styleList);
if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == styleList->mListStylePosition) {
if (HaveOutsideBullet()) {
// We now have an inside bullet, but used to have an outside
// bullet. Adjust the frame line list
nsLineBox* line = aState.NewLineBox(mBullet, 1, PR_FALSE);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
line->mNext = mLines;
mLines = line;
}
mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
else {
if (!HaveOutsideBullet()) {
// We now have an outside bullet, but used to have an inside
// bullet. Take the bullet frame out of the first lines frame
// list.
if ((nsnull != mLines) && (mBullet == mLines->mFirstChild)) {
nsIFrame* next;
mBullet->GetNextSibling(&next);
mBullet->SetNextSibling(nsnull);
PRInt32 count = mLines->GetChildCount() - 1;
NS_ASSERTION(count >= 0, "empty line w/o bullet");
mLines->SetChildCount(count);
if (0 == count) {
nsLineBox* nextLine = mLines->mNext;
aState.FreeLineBox(mLines);
mLines = nextLine;
if (nsnull != nextLine) {
nextLine->MarkDirty();
}
}
else {
mLines->mFirstChild = next;
mLines->MarkDirty();
}
}
}
mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return NS_OK;
}
nsresult
nsBlockFrame::PrepareStyleChangedReflow(nsBlockReflowState& aState)
{
nsresult rv = UpdateBulletPosition(aState);
1999-04-28 02:10:51 +04:00
// Mark everything dirty
nsLineBox* line = mLines;
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
return rv;
}
nsresult
nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState)
{
// See if we can try and avoid marking all the lines as dirty
PRBool tryAndSkipLines = PR_FALSE;
// we need to calculate if any part of then block itself
// is impacted by a floater (bug 19579)
aState.GetAvailableSpace();
// See if this is this a constrained resize reflow that is not impacted by floaters
if ((PR_FALSE==aState.IsImpactedByFloater()) &&
(aState.mReflowState.reason == eReflowReason_Resize) &&
(NS_UNCONSTRAINEDSIZE != aState.mReflowState.availableWidth)) {
// If the text is left-aligned, then we try and avoid reflowing the lines
const nsStyleText* styleText = (const nsStyleText*)
mStyleContext->GetStyleData(eStyleStruct_Text);
if ((NS_STYLE_TEXT_ALIGN_LEFT == styleText->mTextAlign) ||
((NS_STYLE_TEXT_ALIGN_DEFAULT == styleText->mTextAlign) &&
(NS_STYLE_DIRECTION_LTR == aState.mReflowState.mStyleDisplay->mDirection))) {
tryAndSkipLines = PR_TRUE;
}
}
#ifdef DEBUG
if (gDisableResizeOpt) {
tryAndSkipLines = PR_FALSE;
}
if (gNoisyReflow) {
if (!tryAndSkipLines) {
const nsStyleText* mStyleText = (const nsStyleText*)
mStyleContext->GetStyleData(eStyleStruct_Text);
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": marking all lines dirty: reason=%d availWidth=%d textAlign=%d\n",
aState.mReflowState.reason,
aState.mReflowState.availableWidth,
mStyleText->mTextAlign);
}
}
#endif
nsLineBox* line = mLines;
if (tryAndSkipLines) {
nscoord newAvailWidth = aState.mReflowState.mComputedBorderPadding.left;
if (NS_SHRINKWRAPWIDTH == aState.mReflowState.mComputedWidth) {
if (NS_UNCONSTRAINEDSIZE != aState.mReflowState.mComputedMaxWidth) {
newAvailWidth += aState.mReflowState.mComputedMaxWidth;
}
else {
newAvailWidth += aState.mReflowState.availableWidth;
}
} else {
if (NS_UNCONSTRAINEDSIZE != aState.mReflowState.mComputedMaxWidth) {
newAvailWidth += aState.mReflowState.mComputedWidth;
}
else {
newAvailWidth += aState.mReflowState.availableWidth;
}
}
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != newAvailWidth, "bad math, newAvailWidth is infinite");
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": trying to avoid marking all lines dirty\n");
}
#endif
PRBool notWrapping = aState.GetFlag(BRS_NOWRAP);
while (nsnull != line) {
if (line->IsBlock()) {
// We have to let child blocks make their own decisions.
line->MarkDirty();
}
else {
// We can avoid reflowing *some* inline lines in some cases.
#ifdef REALLY_NOISY_REFLOW
PRINTF("PrepareResizeReflow thinks line %p is %simpacted by floaters\n",
line, line->IsImpactedByFloater() ? "" : "not ");
#endif
if (notWrapping) {
// When no-wrap is set then the only line-breaking that
// occurs for inline lines is triggered by BR elements or by
// newlines. Therefore, we don't need to reflow the line.
}
else if ((line->mNext && !line->HasBreak()) ||
line->ResizeReflowOptimizationDisabled() ||
line->HasFloaters() || line->IsImpactedByFloater() ||
line->HasPercentageChild() ||
(line->mBounds.XMost() > newAvailWidth)) {
// When an inline line has:
//
// - a next line and it doesn't end in a break, or
// - floaters, or
// - is impacted by a floater, or
// - is wider than the new available space
//
// Then we must reflow it.
line->MarkDirty();
}
#ifdef DEBUG
if (gNoisyReflow && !line->IsDirty() && !notWrapping) {
IndentBy(stdout, gNoiseIndent + 1);
PRINTF("skipped: line=%p next=%p %s %s %s%s%s breakType=%d xmost=%d\n",
line, line->mNext,
line->IsBlock() ? "block" : "inline",
aState.GetFlag(BRS_NOWRAP) ? "no-wrap" : "wrapping",
line->HasBreak() ? "has-break " : "",
line->HasFloaters() ? "has-floaters " : "",
line->IsImpactedByFloater() ? "impacted " : "",
line->GetBreakType(),
line->mBounds.XMost());
}
#endif
}
line = line->mNext;
}
}
else {
// Mark everything dirty
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
}
return NS_OK;
}
//----------------------------------------
nsLineBox*
nsBlockFrame::FindLineFor(nsIFrame* aFrame,
nsLineBox** aPrevLineResult,
PRBool* aIsFloaterResult)
{
nsLineBox* prevLine = nsnull;
nsLineBox* line = mLines;
PRBool isFloater = PR_FALSE;
while (nsnull != line) {
if (line->Contains(aFrame)) {
break;
}
if (line->HasFloaters()) {
nsFloaterCache* fc = line->GetFirstFloater();
while (fc) {
if (aFrame == fc->mPlaceholder->GetOutOfFlowFrame()) {
isFloater = PR_TRUE;
goto done;
}
fc = fc->Next();
}
}
prevLine = line;
line = line->mNext;
}
done:
*aIsFloaterResult = isFloater;
*aPrevLineResult = prevLine;
return line;
}
// SEC: added GetCurrentLine() for bug 45152
// we need a way for line layout to know what line is being reflowed,
// but we don't want to expose the innards of nsBlockReflowState.
nsresult
nsBlockFrame::GetCurrentLine(nsBlockReflowState *aState, nsLineBox ** aOutCurrentLine)
{
if (!aState || !aOutCurrentLine) return NS_ERROR_FAILURE;
*aOutCurrentLine = aState->mCurrentLine;
return NS_OK;
}
void
nsBlockFrame::RecoverStateFrom(nsBlockReflowState& aState,
nsLineBox* aLine,
nscoord aDeltaY,
nsRect* aDamageRect)
1998-09-15 04:19:49 +04:00
{
PRBool applyTopMargin = PR_FALSE;
if (aLine->IsBlock()) {
nsIFrame* framePrevInFlow;
aLine->mFirstChild->GetPrevInFlow(&framePrevInFlow);
if (nsnull == framePrevInFlow) {
applyTopMargin = ShouldApplyTopMargin(aState, aLine);
}
}
aState.RecoverStateFrom(aLine, applyTopMargin, aDeltaY, aDamageRect);
1998-09-15 04:19:49 +04:00
}
/**
* Propogate reflow "damage" from the just reflowed line (aLine) to
* any subsequent lines that were affected. The only thing that causes
* damage is a change to the impact that floaters make.
*/
void
nsBlockFrame::PropogateReflowDamage(nsBlockReflowState& aState,
nsLineBox* aLine,
const nsRect& aOldCombinedArea,
nscoord aDeltaY)
1998-09-15 04:19:49 +04:00
{
// See if the line has a relevant combined area, and if it does if
// the combined area has changed.
nsRect lineCombinedArea;
aLine->GetCombinedArea(&lineCombinedArea);
if (lineCombinedArea != aLine->mBounds) {
if (lineCombinedArea != aOldCombinedArea) {
// The line's combined-area changed. Therefore we need to damage
// the lines below that were previously (or are now) impacted by
// the change. It's possible that a floater shrunk or grew so
// use the larger of the impacted area.
nscoord newYMost = lineCombinedArea.YMost();
nscoord oldYMost = aOldCombinedArea.YMost();
nscoord impactYB = newYMost < oldYMost ? oldYMost : newYMost;
nscoord impactYA = lineCombinedArea.y;
// Loop over each subsequent line and mark them dirty if they
// intersect the impacted area. Note: we cannot stop after the
// first non-intersecting line because lines might be
// overlapping because of negative margins.
nsLineBox* next = aLine->mNext;
while (nsnull != next) {
nscoord lineYA = next->mBounds.y + aDeltaY;
nscoord lineYB = lineYA + next->mBounds.height;
if ((lineYB >= impactYA) && (lineYA < impactYB)) {
next->MarkDirty();
}
next = next->mNext;
}
}
else {
// The line's combined area didn't change from last
// time. Therefore just sliding subsequent lines will work.
return;
}
}
1998-09-15 04:19:49 +04:00
if (aDeltaY) {
nsLineBox* next = aLine->mNext;
while (nsnull != next) {
if (!next->IsDirty()) {
// Cases we need to find:
//
// 1. the line was impacted by a floater and now isn't
// 2. the line wasn't impacted by a floater and now is
//
//XXXPerf: An optimization: if the line was and is completely
//impacted by a floater and the floater hasn't changed size,
//then we don't need to mark the line dirty.
aState.GetAvailableSpace(next->mBounds.y + aDeltaY);
PRBool wasImpactedByFloater = next->IsImpactedByFloater();
PRBool isImpactedByFloater = aState.IsImpactedByFloater() ? PR_TRUE : PR_FALSE;
#ifdef REALLY_NOISY_REFLOW
PRINTF("nsBlockFrame::PropogateReflowDamage %p was = %d, is=%d\n",
this, wasImpactedByFloater, isImpactedByFloater);
#endif
if (wasImpactedByFloater != isImpactedByFloater) {
next->MarkDirty();
}
else if (isImpactedByFloater) {
//XXX: Maybe the floater itself changed size?
if (next->IsBlock()) {
//XXXPerf
// Case:
// It's possible that more/less of the line is impacted by
// the floater than last time. So reflow.
next->MarkDirty();
}
}
}
next = next->mNext;
}
}
1998-09-15 04:19:49 +04:00
}
static PRBool
WrappedLinesAreDirty(nsLineBox* aLine)
{
if (aLine->IsInline()) {
while (aLine->IsLineWrapped()) {
aLine = aLine->mNext;
if (!aLine) {
break;
}
NS_ASSERTION(!aLine->IsBlock(), "didn't expect a block line");
if (aLine->IsDirty()) {
// we found a continuing line that is dirty
return PR_TRUE;
}
}
}
return PR_FALSE;
}
PRBool nsBlockFrame::IsIncrementalDamageConstrained(const nsBlockReflowState& aState) const
{
// see if the reflow will go through a text control. if so, we can optimize
// because we know the text control won't change size.
if (aState.mReflowState.reflowCommand)
{
nsIFrame *target;
aState.mReflowState.reflowCommand->GetTarget(target);
while (target)
{ // starting with the target's parent, scan for a text control
nsIFrame *parent;
target->GetParent(&parent);
if ((nsIFrame*)this==parent || !parent) // the null check is paranoia, it should never happen
break; // we found ourself, so we know there's no text control between us and target
nsCOMPtr<nsIAtom> frameType;
parent->GetFrameType(getter_AddRefs(frameType));
if (frameType)
{
if (nsLayoutAtoms::textInputFrame == frameType.get())
return PR_TRUE; // damage is constrained to the text control innards
}
target = parent; // advance the loop up the frame tree
}
}
return PR_FALSE; // default case, damage is not constrained (or unknown)
}
/**
* Reflow the dirty lines
*/
nsresult
nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
1998-09-15 04:19:49 +04:00
{
nsresult rv = NS_OK;
PRBool keepGoing = PR_TRUE;
1998-09-15 04:19:49 +04:00
#ifdef DEBUG
if (gNoisyReflow) {
if (aState.mReflowState.reason == eReflowReason_Incremental) {
nsIReflowCommand::ReflowType type;
aState.mReflowState.reflowCommand->GetType(type);
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": incrementally reflowing dirty lines: type=%s(%d) isInline=%s",
2000-02-23 01:06:19 +03:00
kReflowCommandType[type], type,
aState.GetFlag(BRS_ISINLINEINCRREFLOW) ? "true" : "false");
}
else {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": reflowing dirty lines");
}
PRINTF(" computedWidth=%d\n", aState.mReflowState.mComputedWidth);
gNoiseIndent++;
}
#endif
// Check whether this is an incremental reflow
PRBool incrementalReflow = aState.mReflowState.reason ==
eReflowReason_Incremental ||
aState.mReflowState.reason ==
eReflowReason_Dirty;
2000-02-23 01:06:19 +03:00
nscoord deltaY = 0;
// Reflow the lines that are already ours
aState.mPrevLine = nsnull;
nsLineBox* line = mLines;
if (aState.GetFlag(BRS_ISINLINEINCRREFLOW) && aState.mNextRCFrame)
2000-02-23 01:06:19 +03:00
{
const nsLineBox* incrTargetLine = aState.mCurrentLine;
aState.mCurrentLine = line;
aState.mPrevLine = nsnull;
while (line && (line != incrTargetLine))
{
nsRect damageRect;
RecoverStateFrom(aState, line, deltaY, incrementalReflow ?
&damageRect : 0);
if (incrementalReflow && !damageRect.IsEmpty()) {
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 4 (%d, %d, %d, %d)\n",
this, damageRect.x, damageRect.y, damageRect.width, damageRect.height);
#endif
2000-02-23 01:06:19 +03:00
Invalidate(aState.mPresContext, damageRect);
}
aState.mPrevLine = line;
line = line->mNext;
aState.AdvanceToNextLine();
}
}
while (nsnull != line) {
#ifdef DEBUG
if (gNoisyReflow) {
nsRect lca;
line->GetCombinedArea(&lca);
IndentBy(stdout, gNoiseIndent);
PRINTF("line=%p mY=%d dirty=%s oldBounds={%d,%d,%d,%d} oldCombinedArea={%d,%d,%d,%d} deltaY=%d mPrevBottomMargin=%d\n",
line, aState.mY, line->IsDirty() ? "yes" : "no",
line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
lca.x, lca.y, lca.width, lca.height,
deltaY, aState.mPrevBottomMargin);
gNoiseIndent++;
}
#endif
// If we're supposed to update our maximum width, then we'll also need to
// reflow this line if it's line wrapped and any of the continuing lines
// are dirty
if (line->IsDirty() || (aState.GetFlag(BRS_COMPUTEMAXWIDTH) && ::WrappedLinesAreDirty(line))) {
// Compute the dirty lines "before" YMost, after factoring in
// the running deltaY value - the running value is implicit in
// aState.mY.
nscoord oldHeight = line->mBounds.height;
nsRect oldCombinedArea;
line->GetCombinedArea(&oldCombinedArea);
// Reflow the dirty line. If it's an incremental reflow, then force
// it to invalidate the dirty area if necessary
PRBool forceInvalidate = PR_FALSE;
if (incrementalReflow) {
forceInvalidate = !IsIncrementalDamageConstrained(aState);
}
rv = ReflowLine(aState, line, &keepGoing, forceInvalidate);
if (NS_FAILED(rv)) {
return rv;
}
if (!keepGoing) {
if (0 == line->GetChildCount()) {
DeleteLine(aState, line);
}
break;
}
nscoord newHeight = line->mBounds.height;
deltaY += newHeight - oldHeight;
// If the next line is clean then check and see if reflowing the
// current line "damaged" the next line. Damage occurs when the
// current line contains floaters that intrude upon the
// subsequent lines.
nsLineBox* next = line->mNext;
if ((nsnull != next) && !next->IsDirty()) {
PropogateReflowDamage(aState, line, oldCombinedArea, deltaY);
}
1998-09-15 04:19:49 +04:00
}
else {
// XXX what if the slid line doesn't fit because we are in a
// vertically constrained situation?
// Recover state as if we reflowed this line
nsRect damageRect;
RecoverStateFrom(aState, line, deltaY, incrementalReflow ?
&damageRect : 0);
if (incrementalReflow && !damageRect.IsEmpty()) {
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 5 (%d, %d, %d, %d)\n",
this, damageRect.x, damageRect.y, damageRect.width, damageRect.height);
#endif
Invalidate(aState.mPresContext, damageRect);
}
1998-09-15 04:19:49 +04:00
}
#ifdef DEBUG
if (gNoisyReflow) {
gNoiseIndent--;
nsRect lca;
line->GetCombinedArea(&lca);
IndentBy(stdout, gNoiseIndent);
PRINTF("line=%p mY=%d newBounds={%d,%d,%d,%d} newCombinedArea={%d,%d,%d,%d} deltaY=%d mPrevBottomMargin=%d\n",
line, aState.mY,
line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
lca.x, lca.y, lca.width, lca.height,
deltaY, aState.mPrevBottomMargin);
}
#endif
1998-09-15 04:19:49 +04:00
// If this is an inline frame then its time to stop
aState.mPrevLine = line;
line = line->mNext;
aState.AdvanceToNextLine();
1998-09-15 04:19:49 +04:00
}
// Pull data from a next-in-flow if we can
while (keepGoing && (nsnull != aState.mNextInFlow)) {
// Grab first line from our next-in-flow
line = aState.mNextInFlow->mLines;
if (nsnull == line) {
aState.mNextInFlow = (nsBlockFrame*) aState.mNextInFlow->mNextInFlow;
continue;
}
// XXX See if the line is not dirty; if it's not maybe we can
// avoid the pullup if it can't fit?
aState.mNextInFlow->mLines = line->mNext;
line->mNext = nsnull;
if (0 == line->GetChildCount()) {
// The line is empty. Try the next one.
NS_ASSERTION(nsnull == line->mFirstChild, "bad empty line");
aState.FreeLineBox(line);
continue;
}
1998-09-15 04:19:49 +04:00
// XXX move to a subroutine: run-in, overflow, pullframe and this do this
// Make the children in the line ours.
nsIFrame* frame = line->mFirstChild;
nsIFrame* lastFrame = nsnull;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
frame->SetParent(this);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsHTMLContainerFrame::ReparentFrameView(aState.mPresContext, frame, mNextInFlow, this);
lastFrame = frame;
frame->GetNextSibling(&frame);
1998-09-15 04:19:49 +04:00
}
lastFrame->SetNextSibling(nsnull);
// Add line to our line list
if (nsnull == aState.mPrevLine) {
NS_ASSERTION(nsnull == mLines, "bad aState.mPrevLine");
mLines = line;
}
else {
NS_ASSERTION(nsnull == aState.mPrevLine->mNext, "bad aState.mPrevLine");
aState.mPrevLine->mNext = line;
aState.mPrevChild->SetNextSibling(line->mFirstChild);
1998-09-15 04:19:49 +04:00
}
// Now reflow it and any lines that it makes during it's reflow
// (we have to loop here because reflowing the line may case a new
// line to be created; see SplitLine's callers for examples of
// when this happens).
while (nsnull != line) {
rv = ReflowLine(aState, line, &keepGoing, incrementalReflow ?
1999-08-01 20:50:52 +04:00
PR_TRUE : PR_FALSE);
if (NS_FAILED(rv)) {
return rv;
}
if (!keepGoing) {
if (0 == line->GetChildCount()) {
DeleteLine(aState, line);
}
break;
}
// If this is an inline frame then its time to stop
aState.mPrevLine = line;
line = line->mNext;
aState.AdvanceToNextLine();
}
}
1999-07-14 21:26:20 +04:00
// Handle an odd-ball case: a list-item with no lines
if (mBullet && HaveOutsideBullet() && !mLines) {
nsHTMLReflowMetrics metrics(nsnull);
ReflowBullet(aState, metrics);
// There are no lines so we have to fake up some y motion so that
// we end up with *some* height.
aState.mY += metrics.height;
1999-07-14 21:26:20 +04:00
}
#ifdef DEBUG
if (gNoisyReflow) {
gNoiseIndent--;
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
PRINTF(": done reflowing dirty lines (status=%x)\n",
aState.mReflowStatus);
}
#endif
1998-09-15 04:19:49 +04:00
return rv;
1998-09-15 04:19:49 +04:00
}
void
nsBlockFrame::DeleteLine(nsBlockReflowState& aState,
nsLineBox* aLine)
{
NS_PRECONDITION(0 == aLine->GetChildCount(), "can't delete !empty line");
if (0 == aLine->GetChildCount()) {
if (nsnull == aState.mPrevLine) {
NS_ASSERTION(aLine == mLines, "huh");
mLines = nsnull;
}
else {
NS_ASSERTION(aState.mPrevLine->mNext == aLine, "bad prev-line");
aState.mPrevLine->mNext = aLine->mNext;
}
aState.FreeLineBox(aLine);
}
}
/**
* Reflow a line. The line will either contain a single block frame
* or contain 1 or more inline frames. aKeepReflowGoing indicates
* whether or not the caller should continue to reflow more lines.
*/
nsresult
nsBlockFrame::ReflowLine(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
1999-08-01 20:50:52 +04:00
PRBool aDamageDirtyArea)
{
nsresult rv = NS_OK;
NS_ABORT_IF_FALSE(aLine->GetChildCount(), "reflowing empty line");
// Setup the line-layout for the new line
aState.mCurrentLine = aLine;
aLine->ClearDirty();
// Now that we know what kind of line we have, reflow it
nsRect oldCombinedArea;
aLine->GetCombinedArea(&oldCombinedArea);
if (aLine->IsBlock()) {
rv = ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
// We expect blocks to damage any area inside their bounds that is
// dirty; however, if the frame changes size or position then we
// need to do some repainting
1999-08-01 20:50:52 +04:00
if (aDamageDirtyArea) {
nsRect lineCombinedArea;
aLine->GetCombinedArea(&lineCombinedArea);
if ((oldCombinedArea.x != lineCombinedArea.x) ||
(oldCombinedArea.y != lineCombinedArea.y)) {
// The block has moved, and so to be safe we need to repaint
// XXX We need to improve on this...
1999-08-01 20:50:52 +04:00
nsRect dirtyRect;
dirtyRect.UnionRect(oldCombinedArea, lineCombinedArea);
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 6 (%d, %d, %d, %d)\n",
this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
#endif
Invalidate(aState.mPresContext, dirtyRect);
} else {
if (oldCombinedArea.width != lineCombinedArea.width) {
1999-08-01 20:50:52 +04:00
nsRect dirtyRect;
// Just damage the vertical strip that was either added or went
// away
1999-08-01 20:50:52 +04:00
dirtyRect.x = PR_MIN(oldCombinedArea.XMost(),
lineCombinedArea.XMost());
dirtyRect.y = lineCombinedArea.y;
1999-08-01 20:50:52 +04:00
dirtyRect.width = PR_MAX(oldCombinedArea.XMost(),
lineCombinedArea.XMost()) -
1999-08-01 20:50:52 +04:00
dirtyRect.x;
dirtyRect.height = PR_MAX(oldCombinedArea.height,
lineCombinedArea.height);
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 7 (%d, %d, %d, %d)\n",
this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
#endif
Invalidate(aState.mPresContext, dirtyRect);
}
if (oldCombinedArea.height != lineCombinedArea.height) {
1999-08-01 20:50:52 +04:00
nsRect dirtyRect;
// Just damage the horizontal strip that was either added or went
// away
dirtyRect.x = lineCombinedArea.x;
1999-08-01 20:50:52 +04:00
dirtyRect.y = PR_MIN(oldCombinedArea.YMost(),
lineCombinedArea.YMost());
1999-08-01 20:50:52 +04:00
dirtyRect.width = PR_MAX(oldCombinedArea.width,
lineCombinedArea.width);
1999-08-01 20:50:52 +04:00
dirtyRect.height = PR_MAX(oldCombinedArea.YMost(),
lineCombinedArea.YMost()) -
1999-08-01 20:50:52 +04:00
dirtyRect.y;
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 8 (%d, %d, %d, %d)\n",
this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
#endif
Invalidate(aState.mPresContext, dirtyRect);
}
}
}
1998-09-15 04:19:49 +04:00
}
else {
aLine->SetLineWrapped(PR_FALSE);
// If we're supposed to update the maximum width, then we'll need to reflow
// the line with an unconstrained width (which will give us the new maximum
// width), then we'll reflow it again with the constrained width.
// We only do this if this is a beginning line, i.e., don't do this for
// lines associated with content that line wrapped (see ReflowDirtyLines()
// for details).
// XXX This approach doesn't work when floaters are involved in which case
// we'll either need to recover the floater state that applies to the
// unconstrained reflow or keep it around in a separate space manager...
PRBool isBeginningLine = !aState.mPrevLine || !aState.mPrevLine->IsLineWrapped();
if (aState.GetFlag(BRS_COMPUTEMAXWIDTH) && isBeginningLine) {
nscoord oldY = aState.mY;
nscoord oldPrevBottomMargin = aState.mPrevBottomMargin;
PRBool oldUnconstrainedWidth = aState.GetFlag(BRS_UNCONSTRAINEDWIDTH);
// First reflow the line with an unconstrained width. When doing this
// we need to set the block reflow state's "mUnconstrainedWidth" variable
// to PR_TRUE so if we encounter a placeholder and then reflow its
// associated floater we don't end up resetting the line's right edge and
// have it think the width is unconstrained...
aState.SetFlag(BRS_UNCONSTRAINEDWIDTH, PR_TRUE);
ReflowInlineFrames(aState, aLine, aKeepReflowGoing, aDamageDirtyArea, PR_TRUE);
aState.mY = oldY;
aState.mPrevBottomMargin = oldPrevBottomMargin;
aState.SetFlag(BRS_UNCONSTRAINEDWIDTH, oldUnconstrainedWidth);
// Update the line's maximum width
aLine->mMaximumWidth = aLine->mBounds.XMost();
#ifdef NOISY_MAXIMUM_WIDTH
PRINTF("nsBlockFrame::ReflowLine block %p line %p setting aLine.mMaximumWidth to %d\n",
this, aLine, aLine->mMaximumWidth);
#endif
aState.UpdateMaximumWidth(aLine->mMaximumWidth);
// Remove any floaters associated with the line from the space
// manager
aLine->RemoveFloatersFromSpaceManager(aState.mSpaceManager);
// Now reflow the line again this time without having it compute
// the maximum width or max-element-size.
// Note: we need to reset both member variables, because the inline
// code examines mComputeMaxElementSize and if there is a placeholder
// on this line the code to reflow the floater looks at both...
nscoord oldComputeMaxElementSize = aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE);
nscoord oldComputeMaximumWidth = aState.GetFlag(BRS_COMPUTEMAXWIDTH);
aState.SetFlag(BRS_COMPUTEMAXELEMENTSIZE, PR_FALSE);
aState.SetFlag(BRS_COMPUTEMAXWIDTH, PR_FALSE);
rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing, aDamageDirtyArea);
aState.SetFlag(BRS_COMPUTEMAXELEMENTSIZE, oldComputeMaxElementSize);
aState.SetFlag(BRS_COMPUTEMAXWIDTH, oldComputeMaximumWidth);
} else {
rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing, aDamageDirtyArea);
if (NS_SUCCEEDED(rv))
{
if (aState.GetFlag(BRS_COMPUTEMAXWIDTH))
{
#ifdef NOISY_MAXIMUM_WIDTH
PRINTF("nsBlockFrame::ReflowLine block %p line %p setting aLine.mMaximumWidth to %d\n",
this, aLine, aLine->mMaximumWidth);
#endif
aState.UpdateMaximumWidth(aLine->mMaximumWidth);
}
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE))
{
#ifdef NOISY_MAX_ELEMENT_SIZE
PRINTF("nsBlockFrame::ReflowLine block %p line %p setting aLine.mMaxElementWidth to %d\n",
this, aLine, aLine->mMaxElementWidth);
#endif
aState.UpdateMaxElementSize(nsSize(aLine->mMaxElementWidth, aLine->mBounds.height));
}
}
}
// We don't really know what changed in the line, so use the union
// of the old and new combined areas
// SEC: added "aLine->IsForceInvalidate()" for bug 45152
if (aDamageDirtyArea || aLine->IsForceInvalidate()) {
aLine->SetForceInvalidate(PR_FALSE); // doing the invalidate now, force flag to off
nsRect combinedArea;
aLine->GetCombinedArea(&combinedArea);
1999-08-01 20:50:52 +04:00
nsRect dirtyRect;
dirtyRect.UnionRect(oldCombinedArea, combinedArea);
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate because %s is true (%d, %d, %d, %d)\n",
this, aDamageDirtyArea ? "aDamageDirtyArea" : "aLine->IsForceInvalidate",
dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
if (aLine->IsForceInvalidate())
PRINTF(" dirty line is %p\n");
#endif
Invalidate(aState.mPresContext, dirtyRect);
}
1998-09-15 04:19:49 +04:00
}
return rv;
1998-09-15 04:19:49 +04:00
}
/**
* Pull frame from the next available location (one of our lines or
* one of our next-in-flows lines).
*/
nsresult
nsBlockFrame::PullFrame(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool aDamageDeletedLines,
nsIFrame*& aFrameResult)
{
nsresult rv = NS_OK;
PRBool stopPulling;
aFrameResult = nsnull;
// First check our remaining lines
while (nsnull != aLine->mNext) {
rv = PullFrame(aState, aLine, &aLine->mNext, PR_FALSE, aDamageDeletedLines,
aFrameResult, stopPulling);
if (NS_FAILED(rv) || stopPulling) {
return rv;
}
}
// Pull frames from the next-in-flow(s) until we can't
nsBlockFrame* nextInFlow = aState.mNextInFlow;
while (nsnull != nextInFlow) {
nsLineBox* line = nextInFlow->mLines;
if (nsnull == line) {
nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow;
aState.mNextInFlow = nextInFlow;
continue;
}
rv = PullFrame(aState, aLine, &nextInFlow->mLines, PR_TRUE, aDamageDeletedLines,
aFrameResult, stopPulling);
if (NS_FAILED(rv) || stopPulling) {
return rv;
}
}
return rv;
}
/**
* Try to pull a frame out of a line pointed at by aFromList. If a
* frame is pulled then aPulled will be set to PR_TRUE. In addition,
* if aUpdateGeometricParent is set then the pulled frames geometric
* parent will be updated (e.g. when pulling from a next-in-flows line
* list).
*
* Note: pulling a frame from a line that is a place-holder frame
* doesn't automatically remove the corresponding floater from the
* line's floater array. This happens indirectly: either the line gets
* emptied (and destroyed) or the line gets reflowed (because we mark
* it dirty) and the code at the top of ReflowLine empties the
* array. So eventually, it will be removed, just not right away.
*/
nsresult
nsBlockFrame::PullFrame(nsBlockReflowState& aState,
nsLineBox* aLine,
nsLineBox** aFromList,
PRBool aUpdateGeometricParent,
PRBool aDamageDeletedLines,
nsIFrame*& aFrameResult,
PRBool& aStopPulling)
1998-09-15 04:19:49 +04:00
{
nsLineBox* fromLine = *aFromList;
NS_ABORT_IF_FALSE(fromLine, "bad line to pull from");
NS_ABORT_IF_FALSE(fromLine->GetChildCount(), "empty line");
NS_ABORT_IF_FALSE(aLine->GetChildCount(), "empty line");
if (fromLine->IsBlock()) {
// If our line is not empty and the child in aFromLine is a block
// then we cannot pull up the frame into this line. In this case
// we stop pulling.
aStopPulling = PR_TRUE;
aFrameResult = nsnull;
}
else {
// Take frame from fromLine
nsIFrame* frame = fromLine->mFirstChild;
aLine->SetChildCount(aLine->GetChildCount() + 1);
PRInt32 fromLineChildCount = fromLine->GetChildCount();
if (0 != --fromLineChildCount) {
// Mark line dirty now that we pulled a child
fromLine->SetChildCount(fromLineChildCount);
fromLine->MarkDirty();
frame->GetNextSibling(&fromLine->mFirstChild);
}
else {
// Free up the fromLine now that it's empty
// Its bounds might need to be redrawn, though.
if (aDamageDeletedLines) {
Invalidate(aState.mPresContext, fromLine->mBounds);
}
*aFromList = fromLine->mNext;
aState.FreeLineBox(fromLine);
}
// Change geometric parents
if (aUpdateGeometricParent) {
// Before we set the new parent frame get the current parent
nsIFrame* oldParentFrame;
frame->GetParent(&oldParentFrame);
frame->SetParent(this);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
NS_ASSERTION(oldParentFrame != this, "unexpected parent frame");
nsHTMLContainerFrame::ReparentFrameView(aState.mPresContext, frame, oldParentFrame, this);
// The frame is being pulled from a next-in-flow; therefore we
// need to add it to our sibling list.
if (nsnull != aState.mPrevChild) {
aState.mPrevChild->SetNextSibling(frame);
}
frame->SetNextSibling(nsnull);
1998-09-15 04:19:49 +04:00
}
// Stop pulling because we found a frame to pull
aStopPulling = PR_TRUE;
aFrameResult = frame;
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
return NS_OK;
}
static void
PlaceFrameView(nsIPresContext* aPresContext,
nsIFrame* aFrame)
{
nsIView* view;
aFrame->GetView(aPresContext, &view);
if (view) {
nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aFrame, view, nsnull);
} else {
nsContainerFrame::PositionChildViews(aPresContext, aFrame);
}
}
1998-09-15 04:19:49 +04:00
void
1999-09-16 23:56:36 +04:00
nsBlockFrame::SlideLine(nsBlockReflowState& aState,
nsLineBox* aLine, nscoord aDY)
1998-09-15 04:19:49 +04:00
{
// Adjust line state
aLine->SlideBy(aDY);
// Adjust the frames in the line
nsIFrame* kid = aLine->mFirstChild;
if (!kid) {
return;
}
if (aLine->IsBlock()) {
nsRect r;
kid->GetRect(r);
1999-09-16 23:56:36 +04:00
if (aDY) {
r.y += aDY;
kid->SetRect(aState.mPresContext, r);
1999-09-16 23:56:36 +04:00
}
// Make sure the frame's view and any child views are updated
::PlaceFrameView(aState.mPresContext, kid);
// If the child has any floaters that impact the space-manager,
// place them now so that they are present in the space-manager
// again (they were removed by the space-manager's frame when
// the reflow began).
nsBlockFrame* bf;
nsresult rv = kid->QueryInterface(kBlockFrameCID, (void**) &bf);
if (NS_SUCCEEDED(rv)) {
1999-09-16 23:56:36 +04:00
// Translate spacemanager to the child blocks upper-left corner
// so that when it places its floaters (which are relative to
// it) the right coordinates are used. Note that we have already
// been translated by our border+padding so factor that in to
// get the right translation.
const nsMargin& bp = aState.BorderPadding();
nscoord dx = r.x - bp.left;
nscoord dy = r.y - bp.top;
aState.mSpaceManager->Translate(dx, dy);
bf->UpdateSpaceManager(aState.mPresContext, aState.mSpaceManager);
aState.mSpaceManager->Translate(-dx, -dy);
}
}
else {
// Adjust the Y coordinate of the frames in the line.
// Note: we need to re-position views even if aDY is 0, because
// one of our parent frames may have moved and so the view's position
// relative to its parent may have changed
nsRect r;
PRInt32 n = aLine->GetChildCount();
while (--n >= 0) {
if (aDY) {
kid->GetRect(r);
r.y += aDY;
kid->SetRect(aState.mPresContext, r);
}
// Make sure the frame's view and any child views are updated
::PlaceFrameView(aState.mPresContext, kid);
kid->GetNextSibling(&kid);
}
}
}
nsresult
nsBlockFrame::UpdateSpaceManager(nsIPresContext* aPresContext,
nsISpaceManager* aSpaceManager)
{
nsLineBox* line = mLines;
while (nsnull != line) {
// Place the floaters in the spacemanager
if (line->HasFloaters()) {
nsFloaterCache* fc = line->GetFirstFloater();
while (fc) {
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
aSpaceManager->AddRectRegion(floater, fc->mRegion);
#ifdef NOISY_SPACEMANAGER
nscoord tx, ty;
aSpaceManager->GetTranslation(tx, ty);
nsFrame::ListTag(stdout, this);
PRINTF(": UpdateSpaceManager: AddRectRegion: txy=%d,%d {%d,%d,%d,%d}\n",
tx, ty,
fc->mRegion.x, fc->mRegion.y,
fc->mRegion.width, fc->mRegion.height);
#endif
fc = fc->Next();
}
}
// Tell kids about the move too
if (line->mFirstChild && line->IsBlock()) {
// If the child has any floaters that impact the space-manager,
// place them now so that they are present in the space-manager
// again (they were removed by the space-manager's frame when
// the reflow began).
nsBlockFrame* bf;
nsresult rv = line->mFirstChild->QueryInterface(kBlockFrameCID,
(void**) &bf);
if (NS_SUCCEEDED(rv)) {
nsPoint origin;
bf->GetOrigin(origin);
// Translate spacemanager to the child blocks upper-left
// corner so that when it places its floaters (which are
// relative to it) the right coordinates are used.
aSpaceManager->Translate(origin.x, origin.y);
bf->UpdateSpaceManager(aPresContext, aSpaceManager);
aSpaceManager->Translate(-origin.x, -origin.y);
}
1998-09-15 04:19:49 +04:00
}
line = line->mNext;
}
1998-09-15 04:19:49 +04:00
return NS_OK;
}
NS_IMETHODIMP
nsBlockFrame::AttributeChanged(nsIPresContext* aPresContext,
nsIContent* aChild,
PRInt32 aNameSpaceID,
nsIAtom* aAttribute,
PRInt32 aHint)
{
nsresult rv = nsBlockFrameSuper::AttributeChanged(aPresContext, aChild,
aNameSpaceID, aAttribute, aHint);
if (NS_OK != rv) {
return rv;
}
if (nsHTMLAtoms::start == aAttribute) {
// XXX Not sure if this is necessary anymore
RenumberLists(aPresContext);
nsCOMPtr<nsIPresShell> shell;
aPresContext->GetShell(getter_AddRefs(shell));
nsIReflowCommand* reflowCmd;
rv = NS_NewHTMLReflowCommand(&reflowCmd, this,
nsIReflowCommand::ContentChanged,
nsnull,
aAttribute);
if (NS_SUCCEEDED(rv)) {
shell->AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
}
else if (nsHTMLAtoms::value == aAttribute) {
const nsStyleDisplay* styleDisplay;
GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) styleDisplay);
if (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) {
nsIFrame* nextAncestor = mParent;
nsBlockFrame* blockParent = nsnull;
// Search for the closest ancestor that's a block frame. We
// make the assumption that all related list items share a
// common block parent.
while (nextAncestor != nsnull) {
if (NS_OK == nextAncestor->QueryInterface(kBlockFrameCID,
(void**)&blockParent)) {
break;
}
nextAncestor->GetParent(&nextAncestor);
}
// Tell the enclosing block frame to renumber list items within
// itself
if (nsnull != blockParent) {
// XXX Not sure if this is necessary anymore
blockParent->RenumberLists(aPresContext);
nsCOMPtr<nsIPresShell> shell;
aPresContext->GetShell(getter_AddRefs(shell));
nsIReflowCommand* reflowCmd;
rv = NS_NewHTMLReflowCommand(&reflowCmd, blockParent,
nsIReflowCommand::ContentChanged,
nsnull,
aAttribute);
if (NS_SUCCEEDED(rv)) {
shell->AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
}
}
}
return rv;
}
nsBlockFrame*
nsBlockFrame::FindFollowingBlockFrame(nsIFrame* aFrame)
{
nsBlockFrame* followingBlockFrame = nsnull;
nsIFrame* frame = aFrame;
for (;;) {
nsIFrame* nextFrame;
frame->GetNextSibling(&nextFrame);
if (nsnull != nextFrame) {
const nsStyleDisplay* display;
nextFrame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) display);
if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) {
followingBlockFrame = (nsBlockFrame*) nextFrame;
break;
}
else if (NS_STYLE_DISPLAY_INLINE == display->mDisplay) {
// If it's a text-frame and it's just whitespace and we are
// in a normal whitespace situation THEN skip it and keep
// going...
// XXX WRITE ME!
}
frame = nextFrame;
}
else
break;
1998-10-03 01:50:53 +04:00
}
return followingBlockFrame;
}
1998-10-03 01:50:53 +04:00
PRBool
nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState,
nsLineBox* aLine)
{
if (aState.GetFlag(BRS_APPLYTOPMARGIN)) {
// Apply short-circuit check to avoid searching the line list
return PR_TRUE;
}
if (!aState.IsAdjacentWithTop()) {
// If we aren't at the top Y coordinate then something of non-zero
// height must have been placed. Therefore the childs top-margin
// applies.
aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE);
return PR_TRUE;
}
// Determine if this line is "essentially" the first line
nsLineBox* line = mLines;
while (line != aLine) {
if (line->IsBlock()) {
// A line which preceeds aLine contains a block; therefore the
// top margin applies.
aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE);
return PR_TRUE;
}
// No need to apply the top margin if the line has floaters. We
// should collapse anyway (bug 44419)
line = line->mNext;
}
// The line being reflowed is "essentially" the first line in the
// block. Therefore its top-margin will be collapsed by the
// generational collapsing logic with its parent (us).
return PR_FALSE;
}
nsIFrame*
nsBlockFrame::GetTopBlockChild()
{
nsIFrame* firstChild = mLines ? mLines->mFirstChild : nsnull;
if (firstChild) {
if (mLines->IsBlock()) {
// Winner
return firstChild;
}
// If the first line is not a block line then the second line must
// be a block line otherwise the top child can't be a block.
nsLineBox* next = mLines->mNext;
if ((nsnull == next) || !next->IsBlock()) {
// There is no line after the first line or its not a block so
// don't bother trying to skip over the first line.
return nsnull;
}
// The only time we can skip over the first line and pretend its
// not there is if the line contains only compressed
// whitespace. If white-space is significant to this frame then we
// can't skip over the line.
const nsStyleText* styleText;
GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText);
if ((NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) ||
(NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace)) {
// Whitespace is significant
return nsnull;
}
// See if each frame is a text frame that contains nothing but
// whitespace.
PRInt32 n = mLines->GetChildCount();
while (--n >= 0) {
nsIContent* content;
nsresult rv = firstChild->GetContent(&content);
if (NS_FAILED(rv) || (nsnull == content)) {
return nsnull;
}
nsITextContent* tc;
rv = content->QueryInterface(kITextContentIID, (void**) &tc);
NS_RELEASE(content);
if (NS_FAILED(rv) || (nsnull == tc)) {
1999-03-27 05:56:52 +03:00
return nsnull;
}
PRBool isws = PR_FALSE;
tc->IsOnlyWhitespace(&isws);
NS_RELEASE(tc);
if (!isws) {
1999-03-27 05:56:52 +03:00
return nsnull;
}
firstChild->GetNextSibling(&firstChild);
}
// If we make it to this point then every frame on the first line
// was compressible white-space. Since we already know that the
// second line contains a block, that block is the
// top-block-child.
return next->mFirstChild;
}
return nsnull;
}
nsresult
nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing)
{
NS_PRECONDITION(*aKeepReflowGoing, "bad caller");
1998-09-15 04:19:49 +04:00
nsresult rv = NS_OK;
nsIFrame* frame = aLine->mFirstChild;
// Prepare the block reflow engine
const nsStyleDisplay* display;
frame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) display);
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState,
aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE),
aState.GetFlag(BRS_COMPUTEMAXWIDTH));
brc.SetNextRCFrame(aState.mNextRCFrame);
// See if we should apply the top margin. If the block frame being
// reflowed is a continuation (non-null prev-in-flow) then we don't
// apply its top margin because its not significant. Otherwise, dig
// deeper.
PRBool applyTopMargin = PR_FALSE;
nsIFrame* framePrevInFlow;
frame->GetPrevInFlow(&framePrevInFlow);
if (nsnull == framePrevInFlow) {
applyTopMargin = ShouldApplyTopMargin(aState, aLine);
}
// Clear past floaters before the block if the clear style is not none
PRUint8 breakType = display->mBreakType;
aLine->SetBreakType(breakType);
if (NS_STYLE_CLEAR_NONE != breakType) {
PRBool alsoApplyTopMargin = aState.ClearPastFloaters(breakType);
if (alsoApplyTopMargin) {
applyTopMargin = PR_TRUE;
}
1999-09-16 23:56:36 +04:00
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
PRINTF(": y=%d child ", aState.mY);
1999-09-16 23:56:36 +04:00
ListTag(stdout, frame);
PRINTF(" has clear of %d => %s, mPrevBottomMargin=%d\n",
breakType,
1999-09-16 23:56:36 +04:00
applyTopMargin ? "applyTopMargin" : "nope",
aState.mPrevBottomMargin);
#endif
}
1998-09-15 04:19:49 +04:00
nscoord topMargin = 0;
if (applyTopMargin) {
// Precompute the blocks top margin value so that we can get the
// correct available space (there might be a floater that's
// already been placed below the aState.mPrevBottomMargin
// Setup a reflowState to get the style computed margin-top value
// The availSpace here is irrelevant to our needs - all we want
// out if this setup is the margin-top value which doesn't depend
// on the childs available space.
nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE);
nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState,
frame, availSpace);
// Now compute the collapsed margin-top value
topMargin =
nsBlockReflowContext::ComputeCollapsedTopMargin(aState.mPresContext,
reflowState);
// And collapse it with the previous bottom margin to get the final value
topMargin =
nsBlockReflowContext::MaxMargin(topMargin, aState.mPrevBottomMargin);
// Temporarily advance the running Y value so that the
// GetAvailableSpace method will return the right available
// space. This undone as soon as the margin is computed.
aState.mY += topMargin;
}
// Compute the available space for the block
aState.GetAvailableSpace();
#ifdef REALLY_NOISY_REFLOW
PRINTF("setting line %p isImpacted to %s\n", aLine, aState.IsImpactedByFloater()?"true":"false");
#endif
PRBool isImpacted = aState.IsImpactedByFloater() ? PR_TRUE : PR_FALSE;
aLine->SetLineIsImpactedByFloater(isImpacted);
nsSplittableType splitType = NS_FRAME_NOT_SPLITTABLE;
frame->IsSplittable(splitType);
nsRect availSpace;
aState.ComputeBlockAvailSpace(frame, splitType, display, availSpace);
// Now put the Y coordinate back and flow the block letting the
// block reflow context compute the same top margin value we just
// computed (sigh).
if (topMargin) {
aState.mY -= topMargin;
availSpace.y -= topMargin;
if (NS_UNCONSTRAINEDSIZE != availSpace.height) {
availSpace.height += topMargin;
}
}
// Reflow the block into the available space
1999-06-08 04:30:26 +04:00
nsReflowStatus frameReflowStatus=NS_FRAME_COMPLETE;
nsMargin computedOffsets;
rv = brc.ReflowBlock(frame, availSpace,
applyTopMargin, aState.mPrevBottomMargin,
aState.IsAdjacentWithTop(),
computedOffsets, frameReflowStatus);
if (PR_TRUE==brc.BlockShouldInvalidateItself()) {
Invalidate(aState.mPresContext, mRect);
}
if (frame == aState.mNextRCFrame) {
// NULL out mNextRCFrame so if we reflow it again we don't think it's still
// an incremental reflow
aState.mNextRCFrame = nsnull;
}
if (NS_FAILED(rv)) {
return rv;
}
aState.mPrevChild = frame;
#if defined(REFLOW_STATUS_COVERAGE)
RecordReflowStatus(PR_TRUE, frameReflowStatus);
#endif
if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) {
// None of the child block fits.
PushLines(aState);
*aKeepReflowGoing = PR_FALSE;
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
}
else {
// Note: line-break-after a block is a nop
// Try to place the child block
PRBool isAdjacentWithTop = aState.IsAdjacentWithTop();
nscoord collapsedBottomMargin;
2000-02-23 01:06:19 +03:00
nsRect combinedArea(0,0,0,0);
*aKeepReflowGoing = brc.PlaceBlock(isAdjacentWithTop, computedOffsets,
&collapsedBottomMargin,
aLine->mBounds, combinedArea);
if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) {
// Mark the line as block so once we known the final shrink wrap width
// we can reflow the block to the correct size
// XXX We don't always need to do this...
aLine->MarkDirty();
aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE);
}
if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH)) {
// Add the right margin to the line's bounnds. That way it will be taken into
// account when we compute our shrink wrap size
nscoord marginRight = brc.GetMargin().right;
if (marginRight != NS_UNCONSTRAINEDSIZE) {
aLine->mBounds.width += marginRight;
}
}
aLine->SetCombinedArea(combinedArea);
if (*aKeepReflowGoing) {
// Some of the child block fit
// Advance to new Y position
nscoord newY = aLine->mBounds.YMost();
aState.mY = newY;
aLine->SetCarriedOutBottomMargin(brc.GetCarriedOutBottomMargin());
// Continue the block frame now if it didn't completely fit in
// the available space.
if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
PRBool madeContinuation;
rv = CreateContinuationFor(aState, aLine, frame, madeContinuation);
if (NS_FAILED(rv)) {
return rv;
}
// Push continuation to a new line, but only if we actually
// made one.
if (madeContinuation) {
frame->GetNextSibling(&frame);
nsLineBox* line = aState.NewLineBox(frame, 1, PR_TRUE);
if (nsnull == line) {
return NS_ERROR_OUT_OF_MEMORY;
}
line->mNext = aLine->mNext;
aLine->mNext = line;
// Do not count the continuation child on the line it used
// to be on
aLine->SetChildCount(aLine->GetChildCount() - 1);
}
// Advance to next line since some of the block fit. That way
// only the following lines will be pushed.
aState.mPrevLine = aLine;
PushLines(aState);
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
*aKeepReflowGoing = PR_FALSE;
// The bottom margin for a block is only applied on the last
// flow block. Since we just continued the child block frame,
// we know that line->mFirstChild is not the last flow block
// therefore zero out the running margin value.
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
PRINTF(": reflow incomplete, frame=");
nsFrame::ListTag(stdout, frame);
PRINTF(" prevBottomMargin=%d, setting to zero\n",
aState.mPrevBottomMargin);
#endif
aState.mPrevBottomMargin = 0;
}
else {
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
PRINTF(": reflow complete for ");
nsFrame::ListTag(stdout, frame);
PRINTF(" prevBottomMargin=%d collapsedBottomMargin=%d\n",
aState.mPrevBottomMargin, collapsedBottomMargin);
#endif
aState.mPrevBottomMargin = collapsedBottomMargin;
}
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
PRINTF(": frame=");
nsFrame::ListTag(stdout, frame);
PRINTF(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n",
aLine->GetCarriedOutBottomMargin(), collapsedBottomMargin,
aState.mPrevBottomMargin);
#endif
// Post-process the "line"
nsSize maxElementSize(0, 0);
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
maxElementSize = brc.GetMaxElementSize();
if (aState.IsImpactedByFloater() &&
(NS_FRAME_SPLITTABLE_NON_RECTANGULAR != splitType)) {
// Add in floater impacts to the lines max-element-size, but
// only if the block element isn't one of us (otherwise the
// floater impacts will be counted twice).
ComputeLineMaxElementSize(aState, aLine, &maxElementSize);
}
}
// If we asked the block to update its maximum width, then record the
// updated value in the line, and update the current maximum width
if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) {
aLine->mMaximumWidth = brc.GetMaximumWidth();
// need to add in margin on block's reported max width (see bug 35964)
const nsMargin& margin = brc.GetMargin();
aLine->mMaximumWidth += margin.left + margin.right;
#ifdef NOISY_MAXIMUM_WIDTH
PRINTF("nsBlockFrame::ReflowBlockFrame parent block %p line %p aLine->mMaximumWidth set to brc.GetMaximumWidth %d, updating aState.mMaximumWidth\n",
this, aLine, aLine->mMaximumWidth);
#endif
aState.UpdateMaximumWidth(aLine->mMaximumWidth);
}
PostPlaceLine(aState, aLine, maxElementSize);
// Place the "marker" (bullet) frame.
//
// According to the CSS2 spec, section 12.6.1, the "marker" box
// participates in the height calculation of the list-item box's
// first line box.
//
// There are exactly two places a bullet can be placed: near the
// first or second line. Its only placed on the second line in a
// rare case: an empty first line followed by a second line that
// contains a block (example: <LI>\n<P>... ). This is where
// the second case can happen.
if (HaveOutsideBullet() &&
((aLine == mLines) ||
((0 == mLines->mBounds.height) && (aLine == mLines->mNext)))) {
// Reflow the bullet
nsHTMLReflowMetrics metrics(nsnull);
ReflowBullet(aState, metrics);
// For bullets that are placed next to a child block, there will
// be no correct ascent value. Therefore, make one up...
nscoord ascent = 0;
const nsStyleFont* font;
frame->GetStyleData(eStyleStruct_Font,
(const nsStyleStruct*&) font);
nsIRenderingContext& rc = *aState.mReflowState.rendContext;
rc.SetFont(font->mFont);
nsIFontMetrics* fm;
rv = rc.GetFontMetrics(fm);
if (NS_SUCCEEDED(rv) && (nsnull != fm)) {
fm->GetMaxAscent(ascent);
NS_RELEASE(fm);
}
rv = NS_OK;
// Tall bullets won't look particularly nice here...
nsRect bbox;
mBullet->GetRect(bbox);
nscoord bulletTopMargin = applyTopMargin ? collapsedBottomMargin : 0;
bbox.y = aState.BorderPadding().top + ascent -
metrics.ascent + bulletTopMargin;
mBullet->SetRect(aState.mPresContext, bbox);
}
}
else {
// None of the block fits. Determine the correct reflow status.
if (aLine == mLines) {
// If it's our very first line then we need to be pushed to
// our parents next-in-flow. Therefore, return break-before
// status for our reflow status.
aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE();
}
else {
// Push the line that didn't fit and any lines that follow it
// to our next-in-flow.
PushLines(aState);
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
}
}
1998-09-15 04:19:49 +04:00
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return rv;
}
#define LINE_REFLOW_OK 0
#define LINE_REFLOW_STOP 1
#define LINE_REFLOW_REDO 2
nsresult
nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRBool aDamageDirtyArea,
PRBool aUpdateMaximumWidth)
{
nsresult rv = NS_OK;
*aKeepReflowGoing = PR_TRUE;
#ifdef DEBUG
PRInt32 spins = 0;
#endif
PRUint8 lineReflowStatus = LINE_REFLOW_REDO;
while (LINE_REFLOW_REDO == lineReflowStatus) {
// Prevent overflowing limited thread stacks by creating
// nsLineLayout from the heap when the frame tree depth gets
// large.
if (aState.mReflowState.mReflowDepth > 30) {//XXX layout-tune.h?
rv = DoReflowInlineFramesMalloc(aState, aLine, aKeepReflowGoing,
&lineReflowStatus,
aUpdateMaximumWidth, aDamageDirtyArea);
}
else {
rv = DoReflowInlineFramesAuto(aState, aLine, aKeepReflowGoing,
&lineReflowStatus,
aUpdateMaximumWidth, aDamageDirtyArea);
}
if (NS_FAILED(rv)) {
break;
}
#ifdef DEBUG
spins++;
if (1000 == spins) {
ListTag(stdout);
PRINTF(": yikes! spinning on a line over 1000 times!\n");
NS_ABORT();
}
#endif
}
return rv;
}
nsresult
nsBlockFrame::DoReflowInlineFramesMalloc(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus,
PRBool aUpdateMaximumWidth,
PRBool aDamageDirtyArea)
{
nsLineLayout* ll = new nsLineLayout(aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState,
aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE));
if (!ll) {
return NS_ERROR_OUT_OF_MEMORY;
}
ll->Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
nsresult rv = DoReflowInlineFrames(aState, *ll, aLine, aKeepReflowGoing,
aLineReflowStatus, aUpdateMaximumWidth, aDamageDirtyArea);
ll->EndLineReflow();
delete ll;
return rv;
}
nsresult
nsBlockFrame::DoReflowInlineFramesAuto(nsBlockReflowState& aState,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus,
PRBool aUpdateMaximumWidth,
PRBool aDamageDirtyArea)
{
nsLineLayout lineLayout(aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState,
aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE));
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
nsresult rv = DoReflowInlineFrames(aState, lineLayout, aLine,
aKeepReflowGoing, aLineReflowStatus,
aUpdateMaximumWidth, aDamageDirtyArea);
lineLayout.EndLineReflow();
return rv;
}
nsresult
nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRUint8* aLineReflowStatus,
PRBool aUpdateMaximumWidth,
PRBool aDamageDirtyArea)
{
// Forget all of the floaters on the line
aLine->FreeFloaters(aState.mFloaterCacheFreeList);
aState.mFloaterCombinedArea.SetRect(0, 0, 0, 0);
aState.mRightFloaterCombinedArea.SetRect(0, 0, 0, 0);
// Setup initial coordinate system for reflowing the inline frames
// into. Apply a previous block frame's bottom margin first.
aState.mY += aState.mPrevBottomMargin;
aState.GetAvailableSpace();
PRBool impactedByFloaters = aState.IsImpactedByFloater() ? PR_TRUE : PR_FALSE;
aLine->SetLineIsImpactedByFloater(impactedByFloaters);
#ifdef REALLY_NOISY_REFLOW
PRINTF("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n",
this, impactedByFloaters);
#endif
const nsMargin& borderPadding = aState.BorderPadding();
nscoord x = aState.mAvailSpaceRect.x + borderPadding.left;
nscoord availWidth = aState.mAvailSpaceRect.width;
nscoord availHeight;
if (aState.GetFlag(BRS_UNCONSTRAINEDHEIGHT)) {
availHeight = NS_UNCONSTRAINEDSIZE;
}
else {
/* XXX get the height right! */
availHeight = aState.mAvailSpaceRect.height;
}
if (aUpdateMaximumWidth) {
availWidth = NS_UNCONSTRAINEDSIZE;
}
aLineLayout.BeginLineReflow(x, aState.mY,
availWidth, availHeight,
impactedByFloaters,
PR_FALSE /*XXX isTopOfPage*/);
// XXX Unfortunately we need to know this before reflowing the first
// inline frame in the line. FIX ME.
if ((0 == aLineLayout.GetLineNumber()) &&
(NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) {
aLineLayout.SetFirstLetterStyleOK(PR_TRUE);
}
// Reflow the frames that are already on the line first
nsresult rv = NS_OK;
PRUint8 lineReflowStatus = LINE_REFLOW_OK;
PRInt32 i;
nsIFrame* frame = aLine->mFirstChild;
aLine->SetHasPercentageChild(PR_FALSE); // To be set by ReflowInlineFrame below
bug 14280 nsTextTransformer.cpp. I moved where we translate the nbsp to a (ascii 32 space character) until after the i18n routines are called, so they can properly account for the space as non-breaking and therefore part of the first word in the block. bug 39901 and 38396 nsHTMLImageLoader.*, nsImageFrame.cpp I backed out the bad fix for 38396, and put in a new fix where I store a little state in the image loader flags for cases where the image gets an unconstrained reflow and has %-based width. This does not handle %-based min-width or max-width, that would be a separate bug that I'll file shortly. But this fixes the vast majority of real cases out there. bug 18754 nsHRFrame.cpp, quirks.css, nsCSSFrameConstructor.cpp, last part of nsLineLayout.cpp in quirks mode, I changed HR from a block element to a replaced inline element that acts like a block, using generated content to get newlines before and after the HR. This isn't ideal, but it gets us backwards compatibility, and ian and dbaron have blessed the approach. bug 50257 nsLineLayout.cpp Did a couple of things in here: * The actual fix is controlled by FIX_BUG_50257 #define symbol. This basically says that an break (BR) will always fit on a line. A more general solution would probably be to round up to the nearest pixel, and if the thing is less than a pixel make it fit on a line. This is a wimpier, safer solution. * I noticed that the way we got the compatibility mode was way out of date, very wasteful. So I fixed that. * I noticed that there were a bunch of redundant SetFlag calls. Since the flag variable is initialized to 0, setting a flag to 0 on a newly created object is a waste. nsBlockFrame.cpp -- just added a comment to some odd looking code, to make sure no one comes along later and breaks it
2000-09-12 01:15:02 +04:00
// need to repeatedly call GetChildCount here, because the child
// count can change during the loop!
for (i = 0; i < aLine->GetChildCount(); i++) {
rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame,
&lineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
if (LINE_REFLOW_OK != lineReflowStatus) {
// It is possible that one or more of next lines are empty
// (because of DeleteChildsNextInFlow). If so, delete them now
// in case we are finished.
nsLineBox* nextLine = aLine->mNext;
while ((nsnull != nextLine) && (0 == nextLine->GetChildCount())) {
// XXX Is this still necessary now that DeleteChildsNextInFlow
// uses DoRemoveFrame?
aLine->mNext = nextLine->mNext;
NS_ASSERTION(nsnull == nextLine->mFirstChild, "bad empty line");
aState.FreeLineBox(nextLine);
nextLine = aLine->mNext;
}
break;
}
frame->GetNextSibling(&frame);
}
// Pull frames and reflow them until we can't
while (LINE_REFLOW_OK == lineReflowStatus) {
rv = PullFrame(aState, aLine, aDamageDirtyArea, frame);
if (NS_FAILED(rv)) {
return rv;
}
if (nsnull == frame) {
break;
}
while (LINE_REFLOW_OK == lineReflowStatus) {
PRInt32 oldCount = aLine->GetChildCount();
rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame,
&lineReflowStatus);
if (NS_FAILED(rv)) {
return rv;
}
if (aLine->GetChildCount() != oldCount) {
// We just created a continuation for aFrame AND its going
// to end up on this line (e.g. :first-letter
// situation). Therefore we have to loop here before trying
// to pull another frame.
frame->GetNextSibling(&frame);
}
else {
break;
}
}
}
if (LINE_REFLOW_REDO == lineReflowStatus) {
// This happens only when we have a line that is impacted by
// floaters and the first element in the line doesn't fit with
// the floaters.
//
// What we do is to advance past the first floater we find and
// then reflow the line all over again.
NS_ASSERTION(aState.IsImpactedByFloater(),
"redo line on totally empty line");
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mAvailSpaceRect.height,
"unconstrained height on totally empty line");
aState.mY += aState.mAvailSpaceRect.height;
// XXX: a small optimization can be done here when paginating:
// if the new Y coordinate is past the end of the block then
// push the line and return now instead of later on after we are
// past the floater.
}
else {
// If we are propogating out a break-before status then there is
// no point in placing the line.
if (!NS_INLINE_IS_BREAK_BEFORE(aState.mReflowStatus)) {
rv = PlaceLine(aState, aLineLayout, aLine, aKeepReflowGoing, aUpdateMaximumWidth);
}
}
*aLineReflowStatus = lineReflowStatus;
return rv;
}
/**
* Reflow an inline frame. The reflow status is mapped from the frames
* reflow status to the lines reflow status (not to our reflow status).
* The line reflow status is simple: PR_TRUE means keep placing frames
* on the line; PR_FALSE means don't (the line is done). If the line
* has some sort of breaking affect then aLine's break-type will be set
* to something other than NS_STYLE_CLEAR_NONE.
*/
nsresult
nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
nsIFrame* aFrame,
PRUint8* aLineReflowStatus)
{
*aLineReflowStatus = LINE_REFLOW_OK;
// If it's currently ok to be reflowing in first-letter style then
// we must be about to reflow a frame that has first-letter style.
PRBool reflowingFirstLetter = aLineLayout.GetFirstLetterStyleOK();
1999-04-28 02:10:51 +04:00
#ifdef NOISY_FIRST_LETTER
ListTag(stdout);
PRINTF(": reflowing ");
1999-04-28 02:10:51 +04:00
nsFrame::ListTag(stdout, aFrame);
PRINTF(" reflowingFirstLetter=%s\n", reflowingFirstLetter ? "on" : "off");
1999-04-28 02:10:51 +04:00
#endif
1998-09-15 04:19:49 +04:00
// Remember if we have a percentage aware child on this line
if (IsPercentageAwareChild(aFrame)) {
aLine->SetHasPercentageChild(PR_TRUE);
}
// Reflow the inline frame
nsReflowStatus frameReflowStatus;
PRBool pushedFrame;
nsresult rv = aLineLayout.ReflowFrame(aFrame, &aState.mNextRCFrame,
frameReflowStatus, nsnull, pushedFrame);
if (aFrame == aState.mNextRCFrame) {
// NULL out mNextRCFrame so if we reflow it again we don't think it's still
// an incremental reflow
aState.mNextRCFrame = nsnull;
}
if (NS_FAILED(rv)) {
return rv;
1998-09-15 04:19:49 +04:00
}
#ifdef REALLY_NOISY_REFLOW_CHILD
nsFrame::ListTag(stdout, aFrame);
PRINTF(": status=%x\n", frameReflowStatus);
#endif
1998-09-15 04:19:49 +04:00
#if defined(REFLOW_STATUS_COVERAGE)
RecordReflowStatus(PR_FALSE, frameReflowStatus);
#endif
1998-09-15 04:19:49 +04:00
// Send post-reflow notification
aState.mPrevChild = aFrame;
1998-09-15 04:19:49 +04:00
/* XXX
This is where we need to add logic to handle some odd behavior.
For one thing, we should usually place at least one thing next
to a left floater, even when that floater takes up all the width on a line.
see bug 22496
*/
// Process the child frames reflow status. There are 5 cases:
// complete, not-complete, break-before, break-after-complete,
// break-after-not-complete. There are two situations: we are a
// block or we are an inline. This makes a total of 10 cases
// (fortunately, there is some overlap).
aLine->SetBreakType(NS_STYLE_CLEAR_NONE);
if (NS_INLINE_IS_BREAK(frameReflowStatus)) {
// Always abort the line reflow (because a line break is the
// minimal amount of break we do).
*aLineReflowStatus = LINE_REFLOW_STOP;
// XXX what should aLine's break-type be set to in all these cases?
PRUint8 breakType = NS_INLINE_GET_BREAK_TYPE(frameReflowStatus);
NS_ASSERTION(breakType != NS_STYLE_CLEAR_NONE, "bad break type");
NS_ASSERTION(NS_STYLE_CLEAR_PAGE != breakType, "no page breaks yet");
if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) {
// Break-before cases.
if (aFrame == aLine->mFirstChild) {
// If we break before the first frame on the line then we must
// be trying to place content where theres no room (e.g. on a
// line with wide floaters). Inform the caller to reflow the
// line after skipping past a floater.
*aLineReflowStatus = LINE_REFLOW_REDO;
}
else {
// It's not the first child on this line so go ahead and split
// the line. We will see the frame again on the next-line.
rv = SplitLine(aState, aLineLayout, aLine, aFrame);
if (NS_FAILED(rv)) {
return rv;
}
// If we're splitting the line because the frame didn't fit and it
// was pushed, then mark the line as having word wrapped. We need to
// know that if we're shrink wrapping our width
if (pushedFrame) {
aLine->SetLineWrapped(PR_TRUE);
}
}
1998-09-15 04:19:49 +04:00
}
else {
// Break-after cases
if (breakType == NS_STYLE_CLEAR_LINE) {
if (!aLineLayout.GetLineEndsInBR()) {
breakType = NS_STYLE_CLEAR_NONE;
}
}
aLine->SetBreakType(breakType);
if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
// Create a continuation for the incomplete frame. Note that the
// frame may already have a continuation.
PRBool madeContinuation;
rv = CreateContinuationFor(aState, aLine, aFrame, madeContinuation);
if (NS_FAILED(rv)) {
return rv;
}
// Remember that the line has wrapped
aLine->SetLineWrapped(PR_TRUE);
}
1998-09-15 04:19:49 +04:00
// Split line, but after the frame just reflowed
nsIFrame* nextFrame;
aFrame->GetNextSibling(&nextFrame);
rv = SplitLine(aState, aLineLayout, aLine, nextFrame);
if (NS_FAILED(rv)) {
return rv;
1998-09-15 04:19:49 +04:00
}
// Mark next line dirty in case SplitLine didn't end up
// pushing any frames.
nsLineBox* next = aLine->mNext;
if ((nsnull != next) && !next->IsBlock()) {
next->MarkDirty();
}
}
}
else if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
// Frame is not-complete, no special breaking status
1998-09-15 04:19:49 +04:00
// Create a continuation for the incomplete frame. Note that the
// frame may already have a continuation.
PRBool madeContinuation;
rv = CreateContinuationFor(aState, aLine, aFrame, madeContinuation);
if (NS_FAILED(rv)) {
return rv;
}
// Remember that the line has wrapped
aLine->SetLineWrapped(PR_TRUE);
1999-04-28 02:10:51 +04:00
// If we are reflowing the first letter frame then don't split the
// line and don't stop the line reflow...
PRBool splitLine = !reflowingFirstLetter;
if (reflowingFirstLetter) {
nsCOMPtr<nsIAtom> frameType;
aFrame->GetFrameType(getter_AddRefs(frameType));
if ((nsLayoutAtoms::inlineFrame == frameType.get()) ||
(nsLayoutAtoms::lineFrame == frameType.get())) {
1999-04-28 02:10:51 +04:00
splitLine = PR_TRUE;
}
}
1998-09-15 04:19:49 +04:00
1999-04-28 02:10:51 +04:00
if (splitLine) {
// Split line after the current frame
*aLineReflowStatus = LINE_REFLOW_STOP;
aFrame->GetNextSibling(&aFrame);
rv = SplitLine(aState, aLineLayout, aLine, aFrame);
if (NS_FAILED(rv)) {
return rv;
}
1999-04-28 02:10:51 +04:00
// Mark next line dirty in case SplitLine didn't end up
// pushing any frames.
nsLineBox* next = aLine->mNext;
if ((nsnull != next) && !next->IsBlock()) {
next->MarkDirty();
}
}
1998-09-15 04:19:49 +04:00
}
return NS_OK;
}
/**
* Create a continuation, if necessary, for aFrame. Place it on the
* same line that aFrame is on. Set aMadeNewFrame to PR_TRUE if a
* new frame is created.
*/
1998-09-15 04:19:49 +04:00
nsresult
nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState,
nsLineBox* aLine,
nsIFrame* aFrame,
PRBool& aMadeNewFrame)
1998-09-15 04:19:49 +04:00
{
aMadeNewFrame = PR_FALSE;
nsresult rv;
nsIFrame* nextInFlow;
rv = CreateNextInFlow(aState.mPresContext, this, aFrame, nextInFlow);
if (NS_FAILED(rv)) {
1998-09-15 04:19:49 +04:00
return rv;
}
if (nsnull != nextInFlow) {
aMadeNewFrame = PR_TRUE;
aLine->SetChildCount(aLine->GetChildCount() + 1);
}
#ifdef DEBUG
VerifyLines(PR_FALSE);
#endif
return rv;
1998-09-15 04:19:49 +04:00
}
nsresult
nsBlockFrame::SplitLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
nsLineBox* aLine,
nsIFrame* aFrame)
1998-10-03 01:50:53 +04:00
{
NS_ABORT_IF_FALSE(aLine->IsInline(), "illegal SplitLine on block line");
PRInt32 pushCount = aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
NS_ABORT_IF_FALSE(pushCount >= 0, "bad push count");
#ifdef DEBUG
if (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("split line: from line=%p pushCount=%d aFrame=", aLine, pushCount);
if (aFrame) {
nsFrame::ListTag(stdout, aFrame);
}
else {
PRINTF("(null)");
}
PRINTF("\n");
if (gReallyNoisyReflow) {
aLine->List(aState.mPresContext, stdout, gNoiseIndent+1);
}
}
#endif
if (0 != pushCount) {
NS_ABORT_IF_FALSE(aLine->GetChildCount() > pushCount, "bad push");
NS_ABORT_IF_FALSE(nsnull != aFrame, "whoops");
// Put frames being split out into their own line
nsLineBox* newLine = aState.NewLineBox(aFrame, pushCount, PR_FALSE);
if (!newLine) {
return NS_ERROR_OUT_OF_MEMORY;
1998-10-03 01:50:53 +04:00
}
newLine->mNext = aLine->mNext;
aLine->mNext = newLine;
aLine->SetChildCount(aLine->GetChildCount() - pushCount);
#ifdef DEBUG
if (gReallyNoisyReflow) {
newLine->List(aState.mPresContext, stdout, gNoiseIndent+1);
}
#endif
1998-10-03 01:50:53 +04:00
// Let line layout know that some frames are no longer part of its
// state.
aLineLayout.SplitLineTo(aLine->GetChildCount());
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
return NS_OK;
1998-10-03 01:50:53 +04:00
}
PRBool
nsBlockFrame::ShouldJustifyLine(nsBlockReflowState& aState, nsLineBox* aLine)
1998-09-15 04:19:49 +04:00
{
nsLineBox* next = aLine->mNext;
while (nsnull != next) {
// There is another line
if (0 != next->GetChildCount()) {
// If the next line is a block line then we must not justify
// this line because it means that this line is the last in a
// group of inline lines.
return !next->IsBlock();
}
1998-09-15 04:19:49 +04:00
// The next line is empty, try the next one
next = next->mNext;
1998-09-15 04:19:49 +04:00
}
// XXX Not sure about this part
// Try our next-in-flows lines to answer the question
nsBlockFrame* nextInFlow = (nsBlockFrame*) mNextInFlow;
while (nsnull != nextInFlow) {
nsLineBox* line = nextInFlow->mLines;
while (nsnull != line) {
if (0 != line->GetChildCount()) {
return !line->IsBlock();
}
line = line->mNext;
}
nextInFlow = (nsBlockFrame*) nextInFlow->mNextInFlow;
}
// This is the last line - so don't allow justification
return PR_FALSE;
}
nsresult
nsBlockFrame::PlaceLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
1999-03-21 00:57:03 +03:00
nsLineBox* aLine,
PRBool* aKeepReflowGoing,
PRBool aUpdateMaximumWidth)
{
nsresult rv = NS_OK;
1999-09-21 04:13:50 +04:00
// Trim extra white-space from the line before placing the frames
PRBool trimmed = aLineLayout.TrimTrailingWhiteSpace();
aLine->SetTrimmed(trimmed);
1999-09-21 04:13:50 +04:00
// Vertically align the frames on this line.
//
// According to the CSS2 spec, section 12.6.1, the "marker" box
// participates in the height calculation of the list-item box's
// first line box.
//
// There are exactly two places a bullet can be placed: near the
// first or second line. Its only placed on the second line in a
// rare case: an empty first line followed by a second line that
// contains a block (example: <LI>\n<P>... ).
//
// For this code, only the first case is possible because this
// method is used for placing a line of inline frames. If the rare
// case is happening then the worst that will happen is that the
// bullet frame will be reflowed twice.
PRBool addedBullet = PR_FALSE;
if (HaveOutsideBullet() && (aLine == mLines) &&
(!aLineLayout.IsZeroHeight() || !aLine->mNext)) {
nsHTMLReflowMetrics metrics(nsnull);
ReflowBullet(aState, metrics);
aLineLayout.AddBulletFrame(mBullet, metrics);
addedBullet = PR_TRUE;
}
nsSize maxElementSize;
nscoord lineAscent;
aLineLayout.VerticalAlignFrames(aLine, maxElementSize, lineAscent);
// Our ascent is the ascent of our first line
if (aLine == mLines) {
mAscent = lineAscent;
}
// See if we're shrink wrapping the width
if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) {
// When determining the line's width we also need to include any
// right floaters that impact us. This represents the shrink wrap
// width of the line
if (aState.IsImpactedByFloater() && !aLine->IsLineWrapped()) {
NS_ASSERTION(aState.mContentArea.width >= aState.mAvailSpaceRect.XMost(), "bad state");
aLine->mBounds.width += aState.mContentArea.width - aState.mAvailSpaceRect.XMost();
}
}
1999-03-21 00:57:03 +03:00
#ifdef DEBUG
{
static nscoord lastHeight = 0;
if (CRAZY_HEIGHT(aLine->mBounds.y)) {
lastHeight = aLine->mBounds.y;
if (abs(aLine->mBounds.y - lastHeight) > CRAZY_H/10) {
nsFrame::ListTag(stdout);
PRINTF(": line=%p y=%d line.bounds.height=%d\n",
aLine, aLine->mBounds.y, aLine->mBounds.height);
}
}
else {
lastHeight = 0;
}
1999-03-21 00:57:03 +03:00
}
#endif
// Only block frames horizontally align their children because
// inline frames "shrink-wrap" around their children (therefore
// there is no extra horizontal space).
const nsStyleText* styleText = (const nsStyleText*)
mStyleContext->GetStyleData(eStyleStruct_Text);
PRBool allowJustify = NS_STYLE_TEXT_ALIGN_JUSTIFY == styleText->mTextAlign
&& !aLineLayout.GetLineEndsInBR() && ShouldJustifyLine(aState, aLine);
PRBool successful = aLineLayout.HorizontalAlignFrames(aLine->mBounds, allowJustify,
aState.GetFlag(BRS_SHRINKWRAPWIDTH));
if (!successful) {
// Mark the line dirty and then later once we've determined the width
// we can do the horizontal alignment
aLine->MarkDirty();
aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE);
}
nsRect combinedArea;
aLineLayout.RelativePositionFrames(combinedArea);
aLine->SetCombinedArea(combinedArea);
if (addedBullet) {
aLineLayout.RemoveBulletFrame(mBullet);
}
1998-10-03 01:50:53 +04:00
// Inline lines do not have margins themselves; however they are
// impacted by prior block margins. If this line ends up having some
// height then we zero out the previous bottom margin value that was
// already applied to the line's starting Y coordinate. Otherwise we
// leave it be so that the previous blocks bottom margin can be
// collapsed with a block that follows.
nscoord newY;
if (aLine->mBounds.height > 0) {
// This line has some height. Therefore the application of the
// previous-bottom-margin should stick.
aState.mPrevBottomMargin = 0;
newY = aLine->mBounds.YMost();
}
else {
// Don't let the previous-bottom-margin value affect the newY
// coordinate (it was applied in ReflowInlineFrames speculatively)
// since the line is empty.
nscoord dy = -aState.mPrevBottomMargin;
newY = aState.mY + dy;
aLine->SlideBy(dy);
// keep our ascent in sync
if (mLines == aLine) {
mAscent += dy;
}
}
// See if the line fit. If it doesn't we need to push it. Our first
// line will always fit.
if ((mLines != aLine) && (newY > aState.mBottomEdge)) {
// Push this line and all of it's children and anything else that
// follows to our next-in-flow
PushLines(aState);
// Stop reflow and whack the reflow status if reflow hasn't
// already been stopped.
if (*aKeepReflowGoing) {
2000-02-23 01:06:19 +03:00
NS_ASSERTION(NS_FRAME_COMPLETE == aState.mReflowStatus,
"lost reflow status");
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
*aKeepReflowGoing = PR_FALSE;
}
return rv;
}
1998-10-03 01:50:53 +04:00
aState.mY = newY;
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
1999-07-14 21:26:20 +04:00
#ifdef NOISY_MAX_ELEMENT_SIZE
IndentBy(stdout, GetDepth());
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
PRINTF("PASS1 ");
1999-07-14 21:26:20 +04:00
}
ListTag(stdout);
PRINTF(": line.floaters=%s band.floaterCount=%d\n",
//aLine->mFloaters.NotEmpty() ? "yes" : "no",
aState.mHaveRightFloaters ? "(have right floaters)" : "",
1999-07-14 21:26:20 +04:00
aState.mBand.GetFloaterCount());
#endif
if (0 != aState.mBand.GetFloaterCount()) {
// Add in floater impacts to the lines max-element-size
ComputeLineMaxElementSize(aState, aLine, &maxElementSize);
}
}
// If we're reflowing the line just to incrementally update the
// maximum width, then don't post-place the line. It's doing work we
// don't need, and it will update things like aState.mKidXMost that
// we don't want updated...
if (aUpdateMaximumWidth) {
// However, we do need to update the max-element-size if requested
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
aState.UpdateMaxElementSize(maxElementSize);
// We also cache the max element width in the line. This is needed for
// incremental reflow
aLine->mMaxElementWidth = maxElementSize.width;
#ifdef NOISY_MAX_ELEMENT_SIZE
PRINTF ("nsBlockFrame::PlaceLine: %p setting MES for line %p to %d\n",
this, aLine, maxElementSize.width);
#endif
}
} else {
PostPlaceLine(aState, aLine, maxElementSize);
}
// Add the already placed current-line floaters to the line
aLine->AppendFloaters(aState.mCurrentLineFloaters);
// Any below current line floaters to place?
if (aState.mBelowCurrentLineFloaters.NotEmpty()) {
// Reflow the below-current-line floaters, then add them to the
// lines floater list.
aState.PlaceBelowCurrentLineFloaters(aState.mBelowCurrentLineFloaters);
aLine->AppendFloaters(aState.mBelowCurrentLineFloaters);
1998-09-15 04:19:49 +04:00
}
// When a line has floaters, factor them into the combined-area
// computations.
if (aLine->HasFloaters()) {
// Combine the floater combined area (stored in aState) and the
// value computed by the line layout code.
nsRect lineCombinedArea;
aLine->GetCombinedArea(&lineCombinedArea);
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
PRINTF(": lineCA=%d,%d,%d,%d floaterCA=%d,%d,%d,%d\n",
lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height,
aState.mFloaterCombinedArea.x, aState.mFloaterCombinedArea.y,
aState.mFloaterCombinedArea.width,
aState.mFloaterCombinedArea.height);
#endif
CombineRects(aState.mFloaterCombinedArea, lineCombinedArea);
if (aState.mHaveRightFloaters &&
(aState.GetFlag(BRS_UNCONSTRAINEDWIDTH) || aState.GetFlag(BRS_SHRINKWRAPWIDTH))) {
// We are reflowing in an unconstrained situation or shrink wrapping and
// have some right floaters. They were placed at the infinite right edge
// which will cause the combined area to be unusable.
//
// To solve this issue, we pretend that the right floaters ended up just
// past the end of the line. Note that the right floater combined area
// we computed as we were going will have as its X coordinate the left
// most edge of all the right floaters. Therefore, to accomplish our goal
// all we do is set that X value to the lines XMost value.
#ifdef NOISY_COMBINED_AREA
PRINTF(" ==> rightFloaterCA=%d,%d,%d,%d lineXMost=%d\n",
aState.mRightFloaterCombinedArea.x,
aState.mRightFloaterCombinedArea.y,
aState.mRightFloaterCombinedArea.width,
aState.mRightFloaterCombinedArea.height,
aLine->mBounds.XMost());
#endif
aState.mRightFloaterCombinedArea.x = aLine->mBounds.XMost();
CombineRects(aState.mRightFloaterCombinedArea, lineCombinedArea);
if (aState.GetFlag(BRS_SHRINKWRAPWIDTH)) {
// Mark the line dirty so we come back and re-place the floater once
// the shrink wrap width is determined
aLine->MarkDirty();
aState.SetFlag(BRS_NEEDRESIZEREFLOW, PR_TRUE);
}
}
aLine->SetCombinedArea(lineCombinedArea);
#ifdef NOISY_COMBINED_AREA
PRINTF(" ==> final lineCA=%d,%d,%d,%d\n",
lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height);
#endif
aState.mHaveRightFloaters = PR_FALSE;
}
// Apply break-after clearing if necessary
PRUint8 breakType = aLine->GetBreakType();
switch (breakType) {
case NS_STYLE_CLEAR_LEFT:
case NS_STYLE_CLEAR_RIGHT:
case NS_STYLE_CLEAR_LEFT_AND_RIGHT:
aState.ClearFloaters(aState.mY, breakType);
break;
}
return rv;
}
// Compute the line's max-element-size by adding into the raw value
// computed by reflowing the contents of the line (aMaxElementSize)
// the impact of floaters on this line or the preceeding lines.
void
nsBlockFrame::ComputeLineMaxElementSize(nsBlockReflowState& aState,
nsLineBox* aLine,
nsSize* aMaxElementSize)
{
nscoord maxWidth, maxHeight;
aState.mBand.GetMaxElementSize(aState.mPresContext, &maxWidth, &maxHeight);
#ifdef NOISY_MAX_ELEMENT_SIZE
IndentBy(stdout, GetDepth());
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
PRINTF("PASS1 ");
}
ListTag(stdout);
PRINTF(": maxFloaterSize=%d,%d\n", maxWidth, maxHeight);
#endif
// If the floaters are wider than the content, then use the maximum
// floater width as the maximum width.
//
// It used to be the case that we would always place some content
// next to a floater, regardless of the amount of available space
// after subtracing off the floaters sizes. This can lead to content
// overlapping floaters, so we no longer do this (and pass CSS2's
// conformance tests). This is not how navigator 4-1 used to do
// things.
if (maxWidth > aMaxElementSize->width) {
aMaxElementSize->width = maxWidth;
}
// Only update the max-element-size's height value if the floater is
// part of the current line.
if (aLine->HasFloaters()) {
// If the maximum-height of the tallest floater is larger than the
// maximum-height of the content then update the max-element-size
// height
if (maxHeight > aMaxElementSize->height) {
aMaxElementSize->height = maxHeight;
}
}
#ifdef NOISY_MAX_ELEMENT_SIZE
PRINTF ("nsBlockFrame::ComputeLineMaxElementSize: %p returning MES %d\n",
this, aMaxElementSize->width);
#endif
}
void
nsBlockFrame::PostPlaceLine(nsBlockReflowState& aState,
nsLineBox* aLine,
const nsSize& aMaxElementSize)
{
// If it's inline elements, then make sure the views are correctly
// positioned and sized
if (aLine->IsInline()) {
nsIFrame* frame = aLine->mFirstChild;
for (PRInt32 i = 0; i < aLine->GetChildCount(); i++) {
::PlaceFrameView(aState.mPresContext, frame);
frame->GetNextSibling(&frame);
}
}
// Update max-element-size
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
aState.UpdateMaxElementSize(aMaxElementSize);
// We also cache the max element width in the line. This is needed for
// incremental reflow
aLine->mMaxElementWidth = aMaxElementSize.width;
#ifdef NOISY_MAX_ELEMENT_SIZE
PRINTF ("nsBlockFrame::PostPlaceLine: %p setting line %p MES %d\n",
this, aLine, aMaxElementSize.width);
#endif
}
// If this is an unconstrained reflow, then cache the line width in the
// line. We'll need this during incremental reflow if we're asked to
// calculate the maximum width
if (aState.GetFlag(BRS_UNCONSTRAINEDWIDTH)) {
#ifdef NOISY_MAXIMUM_WIDTH
PRINTF("nsBlockFrame::PostPlaceLine during UC Reflow of block %p line %p caching max width %d\n",
this, aLine, aLine->mBounds.XMost());
#endif
aLine->mMaximumWidth = aLine->mBounds.XMost();
}
// Update xmost
nscoord xmost = aLine->mBounds.XMost();
1999-03-21 00:57:03 +03:00
#ifdef DEBUG
if (CRAZY_WIDTH(xmost)) {
ListTag(stdout);
PRINTF(": line=%p xmost=%d\n", aLine, xmost);
}
#endif
// If we're shrink wrapping our width and the line was wrapped,
// then make sure we take up all of the available width
if (aState.GetFlag(BRS_SHRINKWRAPWIDTH) && aLine->IsLineWrapped()) {
aState.mKidXMost = aState.BorderPadding().left + aState.mContentArea.width;
#ifdef NOISY_KIDXMOST
PRINTF("%p PostPlaceLine A aState.mKidXMost=%d\n", this, aState.mKidXMost);
#endif
}
else if (xmost > aState.mKidXMost) {
aState.mKidXMost = xmost;
#ifdef NOISY_KIDXMOST
PRINTF("%p PostPlaceLine B aState.mKidXMost=%d\n", this, aState.mKidXMost);
#endif
}
1998-09-15 04:19:49 +04:00
}
void
nsBlockFrame::PushLines(nsBlockReflowState& aState)
1998-09-15 04:19:49 +04:00
{
NS_ASSERTION(nsnull != aState.mPrevLine, "bad push");
nsLineBox* lastLine = aState.mPrevLine;
nsLineBox* nextLine = lastLine->mNext;
if (nextLine) {
lastLine->mNext = nsnull;
SetOverflowLines(aState.mPresContext, nextLine);
// Mark all the overflow lines dirty so that they get reflowed when
// they are pulled up by our next-in-flow.
while (nsnull != nextLine) {
nextLine->MarkDirty();
nextLine = nextLine->mNext;
}
}
// Break frame sibling list
nsIFrame* lastFrame = lastLine->LastChild();
lastFrame->SetNextSibling(nsnull);
#ifdef DEBUG
VerifyOverflowSituation(aState.mPresContext);
#endif
}
PRBool
nsBlockFrame::DrainOverflowLines(nsIPresContext* aPresContext)
{
#ifdef DEBUG
VerifyOverflowSituation(aPresContext);
#endif
PRBool drained = PR_FALSE;
nsLineBox* overflowLines;
// First grab the prev-in-flows overflow lines
nsBlockFrame* prevBlock = (nsBlockFrame*) mPrevInFlow;
if (nsnull != prevBlock) {
overflowLines = prevBlock->GetOverflowLines(aPresContext, PR_TRUE);
if (nsnull != overflowLines) {
drained = PR_TRUE;
// Make all the frames on the overflow line list mine
nsIFrame* lastFrame = nsnull;
nsIFrame* frame = overflowLines->mFirstChild;
while (nsnull != frame) {
frame->SetParent(this);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsHTMLContainerFrame::ReparentFrameView(aPresContext, frame, prevBlock, this);
// Get the next frame
lastFrame = frame;
frame->GetNextSibling(&frame);
}
// Join the line lists
if (nsnull == mLines) {
mLines = overflowLines;
}
else {
// Join the sibling lists together
lastFrame->SetNextSibling(mLines->mFirstChild);
// Place overflow lines at the front of our line list
nsLineBox* lastLine = nsLineBox::LastLine(overflowLines);
lastLine->mNext = mLines;
mLines = overflowLines;
}
}
}
// Now grab our own overflow lines
overflowLines = GetOverflowLines(aPresContext, PR_TRUE);
if (overflowLines) {
// This can happen when we reflow and not everything fits and then
// we are told to reflow again before a next-in-flow is created
// and reflows.
nsLineBox* lastLine = nsLineBox::LastLine(mLines);
if (nsnull == lastLine) { // if we had no lines before the drain operation
mLines = overflowLines; // set our mLines to the overflow
}
else { // otherwise, append the overflow to the mLines list
lastLine->mNext = overflowLines;
nsIFrame* lastFrame = lastLine->LastChild();
lastFrame->SetNextSibling(overflowLines->mFirstChild);
}
drained = PR_TRUE;
}
return drained;
}
nsLineBox*
nsBlockFrame::GetOverflowLines(nsIPresContext* aPresContext,
PRBool aRemoveProperty) const
{
nsCOMPtr<nsIPresShell> presShell;
aPresContext->GetShell(getter_AddRefs(presShell));
if (presShell) {
nsCOMPtr<nsIFrameManager> frameManager;
presShell->GetFrameManager(getter_AddRefs(frameManager));
if (frameManager) {
PRUint32 options = 0;
void* value;
if (aRemoveProperty) {
options |= NS_IFRAME_MGR_REMOVE_PROP;
}
frameManager->GetFrameProperty((nsIFrame*)this, nsLayoutAtoms::overflowLinesProperty,
options, &value);
return (nsLineBox*)value;
}
}
return nsnull;
}
// Destructor function for the overflowLines frame property
static void
DestroyOverflowLines(nsIPresContext* aPresContext,
nsIFrame* aFrame,
nsIAtom* aPropertyName,
void* aPropertyValue)
{
if (aPropertyValue) {
nsLineBox* lines = (nsLineBox*) aPropertyValue;
nsLineBox::DeleteLineList(aPresContext, lines);
}
}
nsresult
nsBlockFrame::SetOverflowLines(nsIPresContext* aPresContext,
nsLineBox* aOverflowFrames)
{
nsCOMPtr<nsIPresShell> presShell;
nsresult rv = NS_ERROR_FAILURE;
aPresContext->GetShell(getter_AddRefs(presShell));
if (presShell) {
nsCOMPtr<nsIFrameManager> frameManager;
presShell->GetFrameManager(getter_AddRefs(frameManager));
if (frameManager) {
rv = frameManager->SetFrameProperty(this, nsLayoutAtoms::overflowLinesProperty,
aOverflowFrames, DestroyOverflowLines);
// Verify that we didn't overwrite an existing overflow list
NS_ASSERTION(rv != NS_IFRAME_MGR_PROP_OVERWRITTEN, "existing overflow list");
}
}
return rv;
}
//////////////////////////////////////////////////////////////////////
// Frame list manipulation routines
nsIFrame*
nsBlockFrame::LastChild()
{
if (mLines) {
nsLineBox* line = nsLineBox::LastLine(mLines);
return line->LastChild();
}
return nsnull;
}
NS_IMETHODIMP
nsBlockFrame::AppendFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aFrameList)
{
if (nsnull == aFrameList) {
return NS_OK;
}
if (nsLayoutAtoms::absoluteList == aListName) {
return mAbsoluteContainer.AppendFrames(this, aPresContext, aPresShell, aListName,
aFrameList);
}
else if (nsLayoutAtoms::floaterList == aListName) {
// XXX we don't *really* care about this right now because we are
// BuildFloaterList ing still
mFloaters.AppendFrames(nsnull, aFrameList);
return NS_OK;
}
else if (nsnull != aListName) {
return NS_ERROR_INVALID_ARG;
}
// Find the proper last-child for where the append should go
nsIFrame* lastKid = nsnull;
nsLineBox* lastLine = nsLineBox::LastLine(mLines);
if (lastLine) {
lastKid = lastLine->LastChild();
}
// Add frames after the last child
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": append ");
nsFrame::ListTag(stdout, aFrameList);
if (lastKid) {
PRINTF(" after ");
nsFrame::ListTag(stdout, lastKid);
}
PRINTF("\n");
#endif
nsresult rv = AddFrames(aPresContext, aFrameList, lastKid);
if (NS_SUCCEEDED(rv)) {
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
}
return rv;
}
NS_IMETHODIMP
nsBlockFrame::InsertFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
if (nsLayoutAtoms::absoluteList == aListName) {
return mAbsoluteContainer.InsertFrames(this, aPresContext, aPresShell, aListName,
aPrevFrame, aFrameList);
}
else if (nsLayoutAtoms::floaterList == aListName) {
// XXX we don't *really* care about this right now because we are
// BuildFloaterList'ing still
mFloaters.AppendFrames(nsnull, aFrameList);
return NS_OK;
}
else if (nsnull != aListName) {
return NS_ERROR_INVALID_ARG;
}
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": insert ");
nsFrame::ListTag(stdout, aFrameList);
if (aPrevFrame) {
PRINTF(" after ");
nsFrame::ListTag(stdout, aPrevFrame);
}
PRINTF("\n");
#endif
nsresult rv = AddFrames(aPresContext, aFrameList, aPrevFrame);
if (NS_SUCCEEDED(rv)) {
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
}
return rv;
}
1998-09-15 04:19:49 +04:00
nsresult
nsBlockFrame::AddFrames(nsIPresContext* aPresContext,
nsIFrame* aFrameList,
nsIFrame* aPrevSibling)
1998-09-15 04:19:49 +04:00
{
if (nsnull == aFrameList) {
return NS_OK;
}
nsCOMPtr<nsIPresShell> presShell;
aPresContext->GetShell(getter_AddRefs(presShell));
// Attempt to find the line that contains the previous sibling
nsLineBox* prevSibLine = nsnull;
PRInt32 prevSiblingIndex = -1;
if (aPrevSibling) {
// Find the line that contains the previous sibling
prevSibLine = nsLineBox::FindLineContaining(mLines, aPrevSibling,
&prevSiblingIndex);
NS_ASSERTION(nsnull != prevSibLine, "prev sibling not in line list");
if (nsnull == prevSibLine) {
// Note: defensive code! FindLineContaining must not return
// null in this case, so if it does...
aPrevSibling = nsnull;
}
}
// Find the frame following aPrevSibling so that we can join up the
// two lists of frames.
nsIFrame* prevSiblingNextFrame = nsnull;
if (aPrevSibling) {
aPrevSibling->GetNextSibling(&prevSiblingNextFrame);
// Split line containing aPrevSibling in two if the insertion
// point is somewhere in the middle of the line.
PRInt32 rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
if (rem) {
// Split the line in two where the frame(s) are being inserted.
nsLineBox* line = NS_NewLineBox(presShell, prevSiblingNextFrame, rem, PR_FALSE);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
line->mNext = prevSibLine->mNext;
prevSibLine->mNext = line;
prevSibLine->SetChildCount(prevSibLine->GetChildCount() - rem);
prevSibLine->MarkDirty();
}
// Now (partially) join the sibling lists together
aPrevSibling->SetNextSibling(aFrameList);
}
else if (mLines) {
prevSiblingNextFrame = mLines->mFirstChild;
}
// Walk through the new frames being added and update the line data
// structures to fit.
nsIFrame* newFrame = aFrameList;
while (newFrame) {
PRBool isBlock = nsLineLayout::TreatFrameAsBlock(newFrame);
// If the frame is a block frame, or if there is no previous line
// or if the previous line is a block line then make a new line.
if (isBlock || !prevSibLine || prevSibLine->IsBlock()) {
// Create a new line for the frame and add its line to the line
// list.
nsLineBox* line = NS_NewLineBox(presShell, newFrame, 1, isBlock);
if (!line) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (prevSibLine) {
// Append new line after prevSibLine
line->mNext = prevSibLine->mNext;
prevSibLine->mNext = line;
}
else {
// New line is going before the other lines
line->mNext = mLines;
mLines = line;
}
prevSibLine = line;
}
else {
prevSibLine->SetChildCount(prevSibLine->GetChildCount() + 1);
prevSibLine->MarkDirty();
}
aPrevSibling = newFrame;
newFrame->GetNextSibling(&newFrame);
}
if (prevSiblingNextFrame) {
// Connect the last new frame to the remainder of the sibling list
aPrevSibling->SetNextSibling(prevSiblingNextFrame);
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return NS_OK;
1998-09-15 04:19:49 +04:00
}
void
nsBlockFrame::FixParentAndView(nsIPresContext* aPresContext, nsIFrame* aFrame)
{
while (aFrame) {
nsIFrame* oldParent;
aFrame->GetParent(&oldParent);
aFrame->SetParent(this);
if (this != oldParent) {
nsHTMLContainerFrame::ReparentFrameView(aPresContext, aFrame, oldParent, this);
1999-09-04 03:35:41 +04:00
aPresContext->ReParentStyleContext(aFrame, mStyleContext);
}
aFrame->GetNextSibling(&aFrame);
}
}
NS_IMETHODIMP
nsBlockFrame::RemoveFrame(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame)
{
nsresult rv = NS_OK;
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
PRINTF(": remove ");
nsFrame::ListTag(stdout, aOldFrame);
PRINTF("\n");
#endif
if (nsLayoutAtoms::absoluteList == aListName) {
return mAbsoluteContainer.RemoveFrame(this, aPresContext, aPresShell, aListName, aOldFrame);
}
else if (nsLayoutAtoms::floaterList == aListName) {
// Remove floater from the floater list first
mFloaters.RemoveFrame(aOldFrame);
// Find which line contains the floater
nsLineBox* line = mLines;
while (nsnull != line) {
if (line->IsInline() && line->RemoveFloater(aOldFrame)) {
aOldFrame->Destroy(aPresContext);
goto found_it;
}
line = line->mNext;
}
found_it:
// Mark every line at and below the line where the floater was dirty
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
}
else if (nsnull != aListName) {
rv = NS_ERROR_INVALID_ARG;
}
else {
rv = DoRemoveFrame(aPresContext, aOldFrame);
}
if (NS_SUCCEEDED(rv)) {
// Ask the parent frame to reflow me.
ReflowDirtyChild(&aPresShell, nsnull);
}
return rv;
}
nsresult
nsBlockFrame::DoRemoveFrame(nsIPresContext* aPresContext,
nsIFrame* aDeletedFrame)
1998-09-15 04:19:49 +04:00
{
nsCOMPtr<nsIPresShell> presShell;
aPresContext->GetShell(getter_AddRefs(presShell));
// Find the line and the previous sibling that contains
// deletedFrame; we also find the pointer to the line.
nsBlockFrame* flow = this;
nsLineBox** linep = &flow->mLines;
nsLineBox* line = flow->mLines;
nsLineBox* prevLine = nsnull;
nsIFrame* prevSibling = nsnull;
while (nsnull != line) {
nsIFrame* frame = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
if (frame == aDeletedFrame) {
goto found_frame;
}
prevSibling = frame;
frame->GetNextSibling(&frame);
}
linep = &line->mNext;
prevLine = line;
line = line->mNext;
1998-09-15 04:19:49 +04:00
}
found_frame:;
#ifdef NS_DEBUG
NS_ASSERTION(nsnull != line, "can't find deleted frame in lines");
if (nsnull != prevSibling) {
nsIFrame* tmp;
prevSibling->GetNextSibling(&tmp);
NS_ASSERTION(tmp == aDeletedFrame, "bad prevSibling");
}
#endif
1998-09-15 04:19:49 +04:00
// Remove frame and all of its continuations
while (nsnull != aDeletedFrame) {
while ((nsnull != line) && (nsnull != aDeletedFrame)) {
1998-09-15 04:19:49 +04:00
#ifdef NS_DEBUG
nsIFrame* parent;
aDeletedFrame->GetParent(&parent);
NS_ASSERTION(flow == parent, "messed up delete code");
NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line");
#endif
// See if the frame being deleted is the last one on the line
PRBool isLastFrameOnLine = PR_FALSE;
if (1 == line->GetChildCount()) {
isLastFrameOnLine = PR_TRUE;
}
else if (line->LastChild() == aDeletedFrame) {
isLastFrameOnLine = PR_TRUE;
}
// Remove aDeletedFrame from the line
nsIFrame* nextFrame;
aDeletedFrame->GetNextSibling(&nextFrame);
if (line->mFirstChild == aDeletedFrame) {
line->mFirstChild = nextFrame;
}
if (prevLine && !prevLine->IsBlock()) {
// Since we just removed a frame that follows some inline
// frames, we need to reflow the previous line.
prevLine->MarkDirty();
}
1998-09-15 04:19:49 +04:00
// Take aDeletedFrame out of the sibling list. Note that
// prevSibling will only be nsnull when we are deleting the very
// first frame.
if (nsnull != prevSibling) {
prevSibling->SetNextSibling(nextFrame);
}
// Destroy frame; capture its next-in-flow first in case we need
// to destroy that too.
nsIFrame* nextInFlow;
aDeletedFrame->GetNextInFlow(&nextInFlow);
nsSplittableType st;
aDeletedFrame->IsSplittable(st);
if (NS_FRAME_NOT_SPLITTABLE != st) {
nsSplittableFrame::RemoveFromFlow(aDeletedFrame);
}
#ifdef NOISY_REMOVE_FRAME
PRINTF("DoRemoveFrame: prevLine=%p line=%p frame=",
prevLine, line);
nsFrame::ListTag(stdout, aDeletedFrame);
PRINTF(" prevSibling=%p nextInFlow=%p\n", prevSibling, nextInFlow);
#endif
aDeletedFrame->Destroy(aPresContext);
aDeletedFrame = nextInFlow;
// If line is empty, remove it now
nsLineBox* next = line->mNext;
PRInt32 lineChildCount = line->GetChildCount();
if (0 == --lineChildCount) {
*linep = next;
line->mNext = nsnull;
// Invalidate the space taken up by the line.
// XXX We need to do this if we're removing a frame as a result of
// a call to RemoveFrame(), but we may not need to do this in all
// cases...
nsRect lineCombinedArea;
line->GetCombinedArea(&lineCombinedArea);
#ifdef NOISY_BLOCK_INVALIDATE
PRINTF("%p invalidate 10 (%d, %d, %d, %d)\n",
this, lineCombinedArea.x, lineCombinedArea.y, lineCombinedArea.width, lineCombinedArea.height);
#endif
Invalidate(aPresContext, lineCombinedArea);
line->Destroy(presShell);
line = next;
}
else {
// Make the line that just lost a frame dirty
line->SetChildCount(lineChildCount);
line->MarkDirty();
// If we just removed the last frame on the line then we need
// to advance to the next line.
if (isLastFrameOnLine) {
prevLine = line;
linep = &line->mNext;
line = next;
}
}
// See if we should keep looking in the current flow's line list.
if (nsnull != aDeletedFrame) {
if (aDeletedFrame != nextFrame) {
// The deceased frames continuation is not the next frame in
// the current flow's frame list. Therefore we know that the
// continuation is in a different parent. So break out of
// the loop so that we advance to the next parent.
#ifdef NS_DEBUG
nsIFrame* checkParent;
aDeletedFrame->GetParent(&checkParent);
NS_ASSERTION(checkParent != flow, "strange continuation");
#endif
break;
}
}
1998-09-15 04:19:49 +04:00
}
// Advance to next flow block if the frame has more continuations
if (flow && aDeletedFrame) {
flow = (nsBlockFrame*) flow->mNextInFlow;
NS_ASSERTION(nsnull != flow, "whoops, continuation without a parent");
// add defensive pointer check for bug 56894
if(flow) {
prevLine = nsnull;
line = flow->mLines;
linep = &flow->mLines;
prevSibling = nsnull;
}
}
1998-09-15 04:19:49 +04:00
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return NS_OK;
1998-09-15 04:19:49 +04:00
}
void
nsBlockFrame::DeleteChildsNextInFlow(nsIPresContext* aPresContext,
nsIFrame* aChild)
{
NS_PRECONDITION(IsChild(aPresContext, aChild), "bad geometric parent");
nsIFrame* nextInFlow;
aChild->GetNextInFlow(&nextInFlow);
NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow");
nsBlockFrame* parent;
nextInFlow->GetParent((nsIFrame**)&parent);
NS_PRECONDITION(nsnull != parent, "next-in-flow with no parent");
NS_PRECONDITION(nsnull != parent->mLines, "next-in-flow with weird parent");
// NS_PRECONDITION(nsnull == parent->mOverflowLines, "parent with overflow");
parent->DoRemoveFrame(aPresContext, nextInFlow);
1998-10-03 01:50:53 +04:00
}
////////////////////////////////////////////////////////////////////////
// Floater support
nsresult
nsBlockFrame::ReflowFloater(nsBlockReflowState& aState,
nsPlaceholderFrame* aPlaceholder,
nsRect& aCombinedRect,
1999-07-14 21:26:20 +04:00
nsMargin& aMarginResult,
nsMargin& aComputedOffsetsResult)
1998-10-03 01:50:53 +04:00
{
#ifdef NOISY_FLOATER
PRINTF("Reflow Floater %p in parent %p, availSpace(%d,%d,%d,%d)\n",
aPlaceholder->GetOutOfFlowFrame(), this,
aState.mAvailSpaceRect.x, aState.mAvailSpaceRect.y,
aState.mAvailSpaceRect.width, aState.mAvailSpaceRect.height
);
#endif
// XXX update this just
aState.GetAvailableSpace();
// Reflow the floater. Since floaters are continued we given them an
// unbounded height. Floaters with an auto width are sized to zero
// according to the css2 spec.
// XXX We also need to take into account whether we should clear any
// preceeding floaters...
// XXX Why do we have to add in our border/padding?
nsRect availSpace(aState.mAvailSpaceRect.x + aState.BorderPadding().left,
aState.mAvailSpaceRect.y + aState.BorderPadding().top,
aState.mAvailSpaceRect.width, NS_UNCONSTRAINEDSIZE);
nsIFrame* floater = aPlaceholder->GetOutOfFlowFrame();
PRBool isAdjacentWithTop = aState.IsAdjacentWithTop();
// Setup block reflow state to reflow the floater
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState,
aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE),
aState.GetFlag(BRS_COMPUTEMAXWIDTH));
brc.SetNextRCFrame(aState.mNextRCFrame);
// Reflow the floater
nsReflowStatus frameReflowStatus;
nsresult rv = brc.ReflowBlock(floater, availSpace, PR_TRUE, 0,
isAdjacentWithTop,
1999-07-14 21:26:20 +04:00
aComputedOffsetsResult, frameReflowStatus);
if (PR_TRUE==brc.BlockShouldInvalidateItself()) {
Invalidate(aState.mPresContext, mRect);
}
if (floater == aState.mNextRCFrame) {
// NULL out mNextRCFrame so if we reflow it again we don't think it's still
// an incremental reflow
aState.mNextRCFrame = nsnull;
}
if (NS_FAILED(rv)) {
return rv;
}
// Capture the margin information for the caller
const nsMargin& m = brc.GetMargin();
aMarginResult.top = brc.GetTopMargin();
aMarginResult.right = m.right;
aMarginResult.bottom =
nsBlockReflowContext::MaxMargin(brc.GetCarriedOutBottomMargin(),
m.bottom);
aMarginResult.left = m.left;
const nsHTMLReflowMetrics& metrics = brc.GetMetrics();
aCombinedRect = metrics.mOverflowArea;
// Set the rect, make sure the view is properly sized and positioned,
// and tell the frame we're done reflowing it
floater->SizeTo(aState.mPresContext, metrics.width, metrics.height);
nsIView* view;
floater->GetView(aState.mPresContext, &view);
if (view) {
nsContainerFrame::SyncFrameViewAfterReflow(aState.mPresContext, floater, view,
&metrics.mOverflowArea,
NS_FRAME_NO_MOVE_VIEW);
}
floater->DidReflow(aState.mPresContext, NS_FRAME_REFLOW_FINISHED);
// If we computed it, then stash away the max-element-size for later
if (aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE)) {
nsSize mes = brc.GetMaxElementSize();
mes.SizeBy(aMarginResult.left + aMarginResult.right,
aMarginResult.top + aMarginResult.bottom);
aState.StoreMaxElementSize(floater, mes);
}
#ifdef NOISY_FLOATER
PRINTF("end ReflowFloater %p, sized to %d,%d\n", floater, metrics.width, metrics.height);
#endif
return NS_OK;
}
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);
}
1998-09-15 04:19:49 +04:00
// 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.
void
nsBlockReflowState::AddFloater(nsLineLayout& aLineLayout,
nsPlaceholderFrame* aPlaceholder,
PRBool aInitialReflow)
{
NS_PRECONDITION(nsnull != 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);
// Reflow the floater
mBlock->ReflowFloater(*this, aPlaceholder, fc->mCombinedArea,
fc->mMargins, fc->mOffsets);
// And then place it
PRBool isLeftFloater;
PlaceFloater(fc, &isLeftFloater);
1998-10-03 01:50:53 +04:00
// 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());
1998-10-03 01:50:53 +04:00
// Restore coordinate system
mSpaceManager->Translate(dx, dy);
1998-09-15 04:19:49 +04:00
}
else {
// This floater will be placed after the line is done (it is a
// below-current-line floater).
mBelowCurrentLineFloaters.Append(fc);
1998-09-15 04:19:49 +04:00
}
}
1998-09-15 04:19:49 +04:00
PRBool
nsBlockReflowState::IsLeftMostChild(nsIPresContext* aPresContext, nsIFrame* aFrame)
{
for (;;) {
nsIFrame* parent;
aFrame->GetParent(&parent);
if (parent == mBlock) {
nsIFrame* child = mCurrentLine->mFirstChild;
PRInt32 n = mCurrentLine->GetChildCount();
while ((nsnull != child) && (aFrame != child) && (--n >= 0)) {
nsSize size;
// Is the child zero-sized?
child->GetSize(size);
if (size.width > 0) {
// We found a non-zero sized child frame that precedes aFrame
return PR_FALSE;
}
child->GetNextSibling(&child);
1998-09-15 04:19:49 +04:00
}
break;
1998-09-15 04:19:49 +04:00
}
else {
// See if there are any non-zero sized child frames that precede
// aFrame in the child list
nsIFrame* child;
parent->FirstChild(aPresContext, nsnull, &child);
while ((nsnull != child) && (aFrame != child)) {
nsSize size;
1998-09-15 04:19:49 +04:00
// Is the child zero-sized?
child->GetSize(size);
if (size.width > 0) {
// We found a non-zero sized child frame that precedes aFrame
return PR_FALSE;
}
child->GetNextSibling(&child);
}
1998-09-15 04:19:49 +04:00
}
// 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;
1998-09-15 04:19:49 +04:00
}
return PR_TRUE;
}
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()) {
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.
// XXX It's not clear if it means the higher than the outer edge
// or the border edge or the inner edge?
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::PlaceFloater(nsFloaterCache* aFloaterCache,
PRBool* aIsLeftFloater)
1998-09-15 04:19:49 +04:00
{
// 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;
nsIFrame* floater = aFloaterCache->mPlaceholder->GetOutOfFlowFrame();
1998-09-15 04:19:49 +04:00
// Get the type of floater
const nsStyleDisplay* floaterDisplay;
const nsStyleSpacing* floaterSpacing;
1999-07-14 21:26:20 +04:00
const nsStylePosition* floaterPosition;
floater->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&)floaterDisplay);
floater->GetStyleData(eStyleStruct_Spacing,
(const nsStyleStruct*&)floaterSpacing);
1999-07-14 21:26:20 +04:00
floater->GetStyleData(eStyleStruct_Position,
(const nsStyleStruct*&)floaterPosition);
// See if the floater should clear any preceeding floaters...
if (NS_STYLE_CLEAR_NONE != floaterDisplay->mBreakType) {
ClearFloaters(mY, floaterDisplay->mBreakType);
}
else {
// Get the band of available space
GetAvailableSpace();
1998-09-15 04:19:49 +04:00
}
// Get the floaters bounding box and margin information
nsRect region;
floater->GetRect(region);
// 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");
// While there is not enough room for the floater, clear past other
// floaters until there is room (or the band is not impacted by a
// floater).
//
// Note: The CSS2 spec says that floaters should be placed as high
// as possible.
//
#ifdef FIX_BUG_37657
// Also note that in backwards compatibility mode, we skip this step
// for tables, since in old browsers, floating tables are horizontally
// stacked regardless of available space. (See bug 43086 about
// tables vs. non-tables.)
nsCompatibility mode;
mPresContext->GetCompatibilityMode(&mode);
if ((eCompatibility_NavQuirks != mode) ||
(NS_STYLE_DISPLAY_TABLE != floaterDisplay->mDisplay)) {
while (!CanPlaceFloater(region, floaterDisplay->mFloats)) {
mY += mAvailSpaceRect.height;
GetAvailableSpace();
}
}
#else
while (!CanPlaceFloater(region, floaterDisplay->mFloats)) {
mY += mAvailSpaceRect.height;
GetAvailableSpace();
}
#endif
// 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;
1998-09-15 04:19:49 +04:00
}
else {
isLeftFloater = PR_FALSE;
if (NS_UNCONSTRAINEDSIZE != mAvailSpaceRect.XMost())
region.x = mAvailSpaceRect.XMost() - region.width;
else {
okToAddRectRegion = PR_FALSE;
region.x = mAvailSpaceRect.x;
}
1998-09-15 04:19:49 +04:00
}
*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.
1998-09-15 04:19:49 +04:00
// XXX It's not clear if it means the higher than the outer edge
// or the border edge or the inner edge?
region.y = 0;
1998-09-15 04:19:49 +04:00
}
// 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,
1999-09-16 23:56:36 +04:00
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;
1999-07-14 21:26:20 +04:00
// If floater is relatively positioned, factor that in as well
if (NS_STYLE_POSITION_RELATIVE == floaterPosition->mPosition) {
x += aFloaterCache->mOffsets.left;
y += aFloaterCache->mOffsets.top;
1999-07-14 21:26:20 +04:00
}
// Position the floater and make sure and views are properly positioned
nsIView* view;
floater->MoveTo(mPresContext, x, y);
floater->GetView(mPresContext, &view);
if (view) {
nsContainerFrame::PositionFrameView(mPresContext, floater, view);
} else {
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 {
CombineRects(combinedArea, mRightFloaterCombinedArea);
}
}
else {
CombineRects(combinedArea, mFloaterCombinedArea);
}
// Now restore mY
mY = saveY;
#ifdef DEBUG
if (gNoisyReflow) {
nsRect r;
floater->GetRect(r);
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("placed floater: ");
((nsFrame*)floater)->ListTag(stdout);
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 (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("placing bcl floater: ");
nsFrame::ListTag(stdout, fc->mPlaceholder->GetOutOfFlowFrame());
PRINTF("\n");
}
#endif
mBlock->ReflowFloater(*this, fc->mPlaceholder, fc->mCombinedArea,
fc->mMargins, fc->mOffsets);
// Place the floater
PRBool isLeftFloater;
PlaceFloater(fc, &isLeftFloater);
}
fc = fc->Next();
}
}
void
nsBlockReflowState::ClearFloaters(nscoord aY, PRUint8 aBreakType)
1998-10-27 19:51:02 +03:00
{
#ifdef DEBUG
if (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("clear floaters: in: mY=%d aY=%d(%d)\n",
mY, aY, aY - BorderPadding().top);
}
#endif
1998-10-27 19:51:02 +03:00
1999-09-16 23:56:36 +04:00
#ifdef NOISY_FLOATER_CLEARING
PRINTF("nsBlockReflowState::ClearFloaters: aY=%d breakType=%dn",
1999-09-16 23:56:36 +04:00
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 (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
PRINTF("clear floaters: out: mY=%d(%d)\n", mY, mY - bp.top);
}
#endif
}
1998-10-27 19:51:02 +03:00
//////////////////////////////////////////////////////////////////////
// Painting, event handling
1998-09-15 04:19:49 +04:00
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;
}
#ifdef DEBUG
static void ComputeCombinedArea(nsLineBox* aLine,
nscoord aWidth, nscoord aHeight,
nsRect& aResult)
{
nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
while (nsnull != aLine) {
// Compute min and max x/y values for the reflowed frame's
// combined areas
nsRect lineCombinedArea;
aLine->GetCombinedArea(&lineCombinedArea);
nscoord x = lineCombinedArea.x;
nscoord y = lineCombinedArea.y;
nscoord xmost = x + lineCombinedArea.width;
nscoord ymost = y + lineCombinedArea.height;
if (x < xa) {
xa = x;
}
if (xmost > xb) {
xb = xmost;
}
if (y < ya) {
ya = y;
}
if (ymost > yb) {
yb = ymost;
}
aLine = aLine->mNext;
}
aResult.x = xa;
aResult.y = ya;
aResult.width = xb - xa;
aResult.height = yb - ya;
}
#endif
NS_IMETHODIMP
nsBlockFrame::Paint(nsIPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
if (NS_FRAME_IS_UNFLOWABLE & mState) {
return NS_OK;
}
#ifdef DEBUG
if (gNoisyDamageRepair) {
if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) {
PRInt32 depth = GetDepth();
nsRect ca;
ComputeCombinedArea(mLines, mRect.width, mRect.height, ca);
nsFrame::IndentBy(stdout, depth);
ListTag(stdout);
PRINTF(": bounds=%d,%d,%d,%d dirty=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
mRect.x, mRect.y, mRect.width, mRect.height,
aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height,
ca.x, ca.y, ca.width, ca.height);
}
1999-03-22 23:46:09 +03:00
}
#endif
const nsStyleDisplay* disp = (const nsStyleDisplay*)
mStyleContext->GetStyleData(eStyleStruct_Display);
// Only paint the border and background if we're visible
if (disp->IsVisible() && (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) &&
(0 != mRect.width) && (0 != mRect.height)) {
PRIntn skipSides = GetSkipSides();
const nsStyleColor* color = (const nsStyleColor*)
mStyleContext->GetStyleData(eStyleStruct_Color);
const nsStyleSpacing* spacing = (const nsStyleSpacing*)
mStyleContext->GetStyleData(eStyleStruct_Spacing);
1998-09-15 04:19:49 +04:00
// Paint background, border and outline
nsRect rect(0, 0, mRect.width, mRect.height);
nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this,
aDirtyRect, rect, *color, *spacing, 0, 0);
nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this,
aDirtyRect, rect, *spacing, mStyleContext,
skipSides);
nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this,
aDirtyRect, rect, *spacing, mStyleContext, 0);
}
// If overflow is hidden then set the clip rect so that children don't
// leak out of us. Note that because overflow'-clip' only applies to
// the content area we do this after painting the border and background
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
aRenderingContext.PushState();
SetOverflowClipRect(aRenderingContext);
}
// Child elements have the opportunity to override the visibility
// property and display even if the parent is hidden
if (NS_FRAME_PAINT_LAYER_FLOATERS == aWhichLayer) {
PaintFloaters(aPresContext, aRenderingContext, aDirtyRect);
}
1998-12-18 18:54:23 +03:00
PaintChildren(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer);
1998-10-03 01:50:53 +04:00
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
PRBool clipState;
aRenderingContext.PopState(clipState);
1998-09-15 04:19:49 +04:00
}
1998-12-18 18:54:23 +03:00
#if 0
if ((NS_FRAME_PAINT_LAYER_DEBUG == aWhichLayer) && GetShowFrameBorders()) {
// Render the bands in the spacemanager
nsISpaceManager* sm = mSpaceManager;
if (nsnull != sm) {
nsBlockBandData band;
band.Init(sm, nsSize(mRect.width, mRect.height));
nscoord y = 0;
while (y < mRect.height) {
nsRect availArea;
band.GetAvailableSpace(y, availArea);
// Render a box and a diagonal line through the band
aRenderingContext.SetColor(NS_RGB(0,255,0));
aRenderingContext.DrawRect(0, availArea.y,
mRect.width, availArea.height);
aRenderingContext.DrawLine(0, availArea.y,
mRect.width, availArea.YMost());
// Render boxes and opposite diagonal lines around the
// unavailable parts of the band.
PRInt32 i;
for (i = 0; i < band.GetTrapezoidCount(); i++) {
const nsBandTrapezoid* trapezoid = band.GetTrapezoid(i);
if (nsBandTrapezoid::Available != trapezoid->mState) {
nsRect r;
trapezoid->GetRect(r);
if (nsBandTrapezoid::OccupiedMultiple == trapezoid->mState) {
aRenderingContext.SetColor(NS_RGB(0,255,128));
}
else {
aRenderingContext.SetColor(NS_RGB(128,255,0));
}
aRenderingContext.DrawRect(r);
aRenderingContext.DrawLine(r.x, r.YMost(), r.XMost(), r.y);
}
}
y = availArea.YMost();
}
}
}
#endif
return NS_OK;
}
1998-09-15 04:19:49 +04:00
void
nsBlockFrame::PaintFloaters(nsIPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) {
if (!line->HasFloaters()) {
continue;
1998-09-15 04:19:49 +04:00
}
nsFloaterCache* fc = line->GetFirstFloater();
while (fc) {
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
PaintChild(aPresContext, aRenderingContext, aDirtyRect,
floater, NS_FRAME_PAINT_LAYER_BACKGROUND);
PaintChild(aPresContext, aRenderingContext, aDirtyRect,
floater, NS_FRAME_PAINT_LAYER_FLOATERS);
PaintChild(aPresContext, aRenderingContext, aDirtyRect,
floater, NS_FRAME_PAINT_LAYER_FOREGROUND);
fc = fc->Next();
1998-09-15 04:19:49 +04:00
}
}
}
1998-09-15 04:19:49 +04:00
void
nsBlockFrame::PaintChildren(nsIPresContext* aPresContext,
1999-03-22 23:46:09 +03:00
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
#ifdef DEBUG
1999-03-22 23:46:09 +03:00
PRInt32 depth = 0;
if (gNoisyDamageRepair) {
if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) {
depth = GetDepth();
}
}
PRTime start;
PRInt32 drawnLines;
if (gLamePaintMetrics) {
start = PR_Now();
drawnLines = 0;
1999-03-22 23:46:09 +03:00
}
#endif
for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) {
// If the line's combined area (which includes child frames that
// stick outside of the line's bounding box or our bounding box)
// intersects the dirty rect then paint the line.
if (line->CombinedAreaIntersects(aDirtyRect)) {
#ifdef DEBUG
if (gNoisyDamageRepair &&
(NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer)) {
nsRect lineCombinedArea;
line->GetCombinedArea(&lineCombinedArea);
1999-03-22 23:46:09 +03:00
nsFrame::IndentBy(stdout, depth+1);
PRINTF("draw line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
1999-03-22 23:46:09 +03:00
line, line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height);
1999-03-22 23:46:09 +03:00
}
if (gLamePaintMetrics) {
drawnLines++;
}
1999-03-22 23:46:09 +03:00
#endif
nsIFrame* kid = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
1998-12-18 18:54:23 +03:00
PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid,
aWhichLayer);
kid->GetNextSibling(&kid);
}
}
#ifdef DEBUG
1999-03-22 23:46:09 +03:00
else {
if (gNoisyDamageRepair &&
(NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer)) {
nsRect lineCombinedArea;
line->GetCombinedArea(&lineCombinedArea);
1999-03-22 23:46:09 +03:00
nsFrame::IndentBy(stdout, depth+1);
PRINTF("skip line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
1999-03-22 23:46:09 +03:00
line, line->mBounds.x, line->mBounds.y,
line->mBounds.width, line->mBounds.height,
lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height);
1999-03-22 23:46:09 +03:00
}
}
#endif
1998-09-15 04:19:49 +04:00
}
if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) {
if ((nsnull != mBullet) && HaveOutsideBullet()) {
// Paint outside bullets manually
PaintChild(aPresContext, aRenderingContext, aDirtyRect, mBullet,
aWhichLayer);
}
}
#ifdef DEBUG
if (gLamePaintMetrics) {
PRTime end = PR_Now();
PRInt32 numLines = nsLineBox::ListLength(mLines);
if (!numLines) numLines = 1;
PRTime lines, deltaPerLine, delta;
LL_I2L(lines, numLines);
LL_SUB(delta, end, start);
LL_DIV(deltaPerLine, delta, lines);
ListTag(stdout);
char buf[400];
PR_snprintf(buf, sizeof(buf),
": %lld elapsed (%lld per line) lines=%d drawn=%d skip=%d",
delta, deltaPerLine,
numLines, drawnLines, numLines - drawnLines);
PRINTF("%s\n", buf);
}
#endif
}
1998-09-15 04:19:49 +04:00
NS_IMETHODIMP
nsBlockFrame::HandleEvent(nsIPresContext* aPresContext,
nsGUIEvent* aEvent,
nsEventStatus* aEventStatus)
{
nsresult result;
nsCOMPtr<nsIPresShell> shell;
if (aEvent->message == NS_MOUSE_MOVE) {
aPresContext->GetShell(getter_AddRefs(shell));
if (!shell)
return NS_OK;
nsCOMPtr<nsIFrameSelection> frameSelection;
PRBool mouseDown = PR_FALSE;
//check to see if we need to ask the selection controller..
if (mState & NS_FRAME_INDEPENDENT_SELECTION)
{
nsCOMPtr<nsISelectionController> selCon;
result = GetSelectionController(aPresContext, getter_AddRefs(selCon));
if (NS_FAILED(result) || !selCon)
return result?result:NS_ERROR_FAILURE;
frameSelection = do_QueryInterface(selCon);
}
else
shell->GetFrameSelection(getter_AddRefs(frameSelection));
if (!frameSelection || NS_FAILED(frameSelection->GetMouseDownState(&mouseDown)) || !mouseDown)
return NS_OK;//do not handle
}
if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN || aEvent->message == NS_MOUSE_MOVE ||
aEvent->message == NS_MOUSE_LEFT_DOUBLECLICK ) {
nsMouseEvent *me = (nsMouseEvent *)aEvent;
nsIFrame *resultFrame = nsnull;//this will be passed the handle event when we
//can tell who to pass it to
nsCOMPtr<nsILineIterator> it;
nsIFrame *mainframe = this;
nsCOMPtr<nsIFocusTracker> tracker;
aPresContext->GetShell(getter_AddRefs(shell));
if (!shell)
return NS_OK;
result = shell->QueryInterface(NS_GET_IID(nsIFocusTracker),getter_AddRefs(tracker));
result = mainframe->QueryInterface(NS_GET_IID(nsILineIterator),getter_AddRefs(it));
nsIView* parentWithView;
nsPoint origin;
nsPeekOffsetStruct pos;
while(NS_OK == result)
{ //we are starting aloop to allow us to "drill down to the one we want"
mainframe->GetOffsetFromView(aPresContext, origin, &parentWithView);
if (NS_FAILED(result))
return NS_OK;//do not handle
PRInt32 countLines;
result = it->GetNumLines(&countLines);
if (NS_FAILED(result))
return NS_OK;//do not handle
PRInt32 i;
PRInt32 lineFrameCount;
nsIFrame *firstFrame;
nsRect rect;
PRInt32 closestLine = 0;
PRInt32 closestDistance = 999999; //some HUGE number that will always fail first comparison
//incase we hit another block frame.
for (i = 0; i< countLines;i++)
{
PRUint32 flags;
result = it->GetLine(i, &firstFrame, &lineFrameCount,rect,&flags);
if (NS_FAILED(result))
continue;//do not handle
rect+=origin;
rect.width = aEvent->point.x - rect.x+1;//EXTEND RECT TO REACH POINT
if (rect.Contains(aEvent->point.x, aEvent->point.y))
{
closestLine = i;
break;
}
else
{
PRInt32 distance = PR_MIN(abs(rect.y - aEvent->point.y),abs((rect.y + rect.height) - aEvent->point.y));
if (distance < closestDistance)
{
closestDistance = distance;
closestLine = i;
}
else if (distance > closestDistance)
break;//done
}
}
//we will now ask where to go. if we cant find what we want"aka another block frame"
//we drill down again
pos.mTracker = tracker;
pos.mDirection = eDirNext;
pos.mDesiredX = aEvent->point.x;
result = nsFrame::GetNextPrevLineFromeBlockFrame(aPresContext,
&pos,
mainframe,
closestLine-1,
0
);
if (NS_SUCCEEDED(result) && pos.mResultFrame){
if (result == NS_OK)
result = pos.mResultFrame->QueryInterface(NS_GET_IID(nsILineIterator),getter_AddRefs(it));//if this fails thats ok
resultFrame = pos.mResultFrame;
mainframe = resultFrame;
}
else
break;//time to go nothing was found
}
//end while loop. if nssucceeded resutl then keep going that means
//we have successfully hit another block frame and we should keep going.
if (resultFrame)
2000-05-14 07:41:25 +04:00
{
if (NS_COMFALSE == result)
{
nsCOMPtr<nsISelectionController> selCon;
result = GetSelectionController(aPresContext, getter_AddRefs(selCon));
//get the selection controller
if (NS_SUCCEEDED(result) && selCon)
{
PRInt16 displayresult;
selCon->GetDisplaySelection(&displayresult);
if (displayresult == nsISelectionController::SELECTION_OFF)
return NS_OK;//nothing to do we cannot affect selection from here
}
nsCOMPtr<nsIFrameSelection> frameselection;
shell->GetFrameSelection(getter_AddRefs(frameselection));
if (frameselection)
result = frameselection->HandleClick(pos.mResultContent, pos.mContentOffset,
pos.mContentOffsetEnd, me->isShift, PR_FALSE, pos.mPreferLeft);
}
else
result = resultFrame->HandleEvent(aPresContext, aEvent, aEventStatus);//else let the frame/container do what it needs
2000-05-14 07:41:25 +04:00
if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN && !IsMouseCaptured(aPresContext))
CaptureMouse(aPresContext, PR_TRUE);
return result;
}
else
2000-05-14 07:41:25 +04:00
{
/*we have to add this because any frame that overrides nsFrame::HandleEvent for mouse down MUST capture the mouse events!!
if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN && !IsMouseCaptured(aPresContext))
CaptureMouse(aPresContext, PR_TRUE);*/
return NS_OK; //just stop it
2000-05-14 07:41:25 +04:00
}
}
return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
}
NS_IMETHODIMP
nsBlockFrame::GetFrameForPoint(nsIPresContext* aPresContext,
const nsPoint& aPoint,
nsFramePaintLayer aWhichLayer,
nsIFrame** aFrame)
{
nsresult rv;
switch (aWhichLayer) {
case NS_FRAME_PAINT_LAYER_FOREGROUND:
rv = GetFrameForPointUsing(aPresContext, aPoint, nsnull, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame);
if (NS_OK == rv) {
return NS_OK;
}
if (nsnull != mBullet) {
rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::bulletList, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame);
}
return rv;
break;
case NS_FRAME_PAINT_LAYER_FLOATERS:
// we painted our floaters before our children, and thus
// we should check floaters within children first
rv = GetFrameForPointUsing(aPresContext, aPoint, nsnull, NS_FRAME_PAINT_LAYER_FLOATERS, PR_FALSE, aFrame);
if (NS_OK == rv) {
return NS_OK;
}
if (mFloaters.NotEmpty()) {
rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame);
if (NS_OK == rv) {
return NS_OK;
}
rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, NS_FRAME_PAINT_LAYER_FLOATERS, PR_FALSE, aFrame);
if (NS_OK == rv) {
return NS_OK;
}
return GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, NS_FRAME_PAINT_LAYER_BACKGROUND, PR_FALSE, aFrame);
} else {
return NS_ERROR_FAILURE;
}
break;
case NS_FRAME_PAINT_LAYER_BACKGROUND:
// we're a block, so PR_TRUE for consider self
return GetFrameForPointUsing(aPresContext, aPoint, nsnull, NS_FRAME_PAINT_LAYER_BACKGROUND, PR_TRUE, aFrame);
break;
}
// we shouldn't get here
NS_ASSERTION(PR_FALSE, "aWhichLayer was not understood");
return NS_ERROR_FAILURE;
}
1998-09-15 04:19:49 +04:00
NS_IMETHODIMP
nsBlockFrame::ReflowDirtyChild(nsIPresShell* aPresShell, nsIFrame* aChild)
{
if (aChild) {
// See if the child is absolutely positioned
nsFrameState childState;
aChild->GetFrameState(&childState);
if (childState & NS_FRAME_OUT_OF_FLOW) {
const nsStylePosition* position;
aChild->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)position);
if (position->IsAbsolutelyPositioned()) {
// Generate a reflow command to reflow our dirty absolutely
// positioned child frames.
// XXX Note that we don't currently try and coalesce the reflow commands,
// although we should. We can't use the NS_FRAME_HAS_DIRTY_CHILDREN
// flag, because that's used to indicate whether in-flow children are
// dirty...
nsIReflowCommand* reflowCmd;
nsresult rv = NS_NewHTMLReflowCommand(&reflowCmd, this,
nsIReflowCommand::ReflowDirty);
if (NS_SUCCEEDED(rv)) {
reflowCmd->SetChildListName(nsLayoutAtoms::absoluteList);
aPresShell->AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
return rv;
}
}
}
// Mark the line containing the child frame dirty.
if (aChild) {
PRBool isFloater;
nsLineBox* prevLine;
nsLineBox* line = FindLineFor(aChild, &prevLine, &isFloater);
if (!isFloater) {
if (line) MarkLineDirty(line, prevLine);
}
else {
line = mLines;
while (nsnull != line) {
line->MarkDirty();
line = line->mNext;
}
}
}
// Either generate a reflow command to reflow the dirty child or
// coalesce this reflow request with an existing reflow command
if (!(mState & NS_FRAME_HAS_DIRTY_CHILDREN)) {
// If this is the first dirty child,
// post a dirty children reflow command targeted at yourself
mState |= NS_FRAME_HAS_DIRTY_CHILDREN;
nsFrame::CreateAndPostReflowCommand(aPresShell, this,
nsIReflowCommand::ReflowDirty, nsnull, nsnull, nsnull);
}
else {
if (!(mState & NS_FRAME_IS_DIRTY)) {
// Mark yourself as dirty
mState |= NS_FRAME_IS_DIRTY;
// Cancel the dirty children reflow command you posted earlier
nsIReflowCommand::ReflowType type = nsIReflowCommand::ReflowDirty;
aPresShell->CancelReflowCommand(this, &type);
// Pass up the reflow request to the parent frame.
mParent->ReflowDirtyChild(aPresShell, this);
}
}
return NS_OK;
}
//////////////////////////////////////////////////////////////////////
// Debugging
#ifdef NS_DEBUG
static PRBool
InLineList(nsLineBox* aLines, nsIFrame* aFrame)
{
while (nsnull != aLines) {
nsIFrame* frame = aLines->mFirstChild;
PRInt32 n = aLines->GetChildCount();
while (--n >= 0) {
if (frame == aFrame) {
return PR_TRUE;
}
frame->GetNextSibling(&frame);
1998-09-15 04:19:49 +04:00
}
aLines = aLines->mNext;
1998-09-15 04:19:49 +04:00
}
return PR_FALSE;
1998-09-15 04:19:49 +04:00
}
static PRBool
InSiblingList(nsLineBox* aLine, nsIFrame* aFrame)
{
if (nsnull != aLine) {
nsIFrame* frame = aLine->mFirstChild;
while (nsnull != frame) {
if (frame == aFrame) {
return PR_TRUE;
}
frame->GetNextSibling(&frame);
}
}
return PR_FALSE;
}
PRBool
nsBlockFrame::IsChild(nsIPresContext* aPresContext, nsIFrame* aFrame)
{
nsIFrame* parent;
aFrame->GetParent(&parent);
if (parent != (nsIFrame*)this) {
return PR_FALSE;
}
if (InLineList(mLines, aFrame) && InSiblingList(mLines, aFrame)) {
return PR_TRUE;
}
nsLineBox* overflowLines = GetOverflowLines(aPresContext, PR_FALSE);
if (InLineList(overflowLines, aFrame) && InSiblingList(overflowLines, aFrame)) {
return PR_TRUE;
}
return PR_FALSE;
}
NS_IMETHODIMP
nsBlockFrame::VerifyTree() const
1998-09-15 04:19:49 +04:00
{
// XXX rewrite this
1998-09-15 04:19:49 +04:00
return NS_OK;
}
NS_IMETHODIMP
nsBlockFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
{
if (!aHandler || !aResult) {
return NS_ERROR_NULL_POINTER;
}
PRUint32 sum = sizeof(*this);
// Add in size of each line object
nsLineBox* line = mLines;
while (line) {
PRUint32 lineBoxSize;
nsIAtom* atom = line->SizeOf(aHandler, &lineBoxSize);
aHandler->AddSize(atom, lineBoxSize);
line = line->mNext;
}
*aResult = sum;
return NS_OK;
}
#endif
//----------------------------------------------------------------------
1998-09-15 04:19:49 +04:00
NS_IMETHODIMP
nsBlockFrame::Init(nsIPresContext* aPresContext,
nsIContent* aContent,
nsIFrame* aParent,
nsIStyleContext* aContext,
nsIFrame* aPrevInFlow)
{
if (aPrevInFlow) {
// Copy over the block/area frame type flags
nsBlockFrame* blockFrame = (nsBlockFrame*)aPrevInFlow;
SetFlags(blockFrame->mState & NS_BLOCK_FLAGS_MASK);
}
nsresult rv = nsBlockFrameSuper::Init(aPresContext, aContent, aParent,
aContext, aPrevInFlow);
return rv;
}
1999-04-28 02:10:51 +04:00
nsIStyleContext*
nsBlockFrame::GetFirstLetterStyle(nsIPresContext* aPresContext)
{
nsIStyleContext* fls;
aPresContext->ProbePseudoStyleContextFor(mContent,
nsHTMLAtoms::firstLetterPseudo,
mStyleContext, PR_FALSE, &fls);
return fls;
}
NS_IMETHODIMP
nsBlockFrame::SetInitialChildList(nsIPresContext* aPresContext,
nsIAtom* aListName,
nsIFrame* aChildList)
{
nsresult rv = NS_OK;
1998-09-15 04:19:49 +04:00
if (nsLayoutAtoms::absoluteList == aListName) {
mAbsoluteContainer.SetInitialChildList(this, aPresContext, aListName, aChildList);
}
else if (nsLayoutAtoms::floaterList == aListName) {
mFloaters.SetFrames(aChildList);
}
else {
// Lookup up the two pseudo style contexts
if (nsnull == mPrevInFlow) {
nsIStyleContext* firstLetterStyle = GetFirstLetterStyle(aPresContext);
1999-04-28 02:10:51 +04:00
if (nsnull != firstLetterStyle) {
mState |= NS_BLOCK_HAS_FIRST_LETTER_STYLE;
#ifdef NOISY_FIRST_LETTER
ListTag(stdout);
PRINTF(": first-letter style found\n");
1999-04-28 02:10:51 +04:00
#endif
NS_RELEASE(firstLetterStyle);
}
}
rv = AddFrames(aPresContext, aChildList, nsnull);
if (NS_FAILED(rv)) {
return rv;
}
1998-09-15 04:19:49 +04:00
// Create list bullet if this is a list-item. Note that this is done
// here so that RenumberLists will work (it needs the bullets to
// store the bullet numbers).
const nsStyleDisplay* styleDisplay;
GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) styleDisplay);
if ((nsnull == mPrevInFlow) &&
(NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) &&
(nsnull == mBullet)) {
// Resolve style for the bullet frame
nsIStyleContext* kidSC;
aPresContext->ResolvePseudoStyleContextFor(mContent,
nsHTMLAtoms::mozListBulletPseudo,
mStyleContext, PR_FALSE, &kidSC);
// Create bullet frame
nsCOMPtr<nsIPresShell> shell;
aPresContext->GetShell(getter_AddRefs(shell));
mBullet = new (shell.get()) nsBulletFrame;
if (nsnull == mBullet) {
NS_RELEASE(kidSC);
return NS_ERROR_OUT_OF_MEMORY;
}
mBullet->Init(aPresContext, mContent, this, kidSC, nsnull);
NS_RELEASE(kidSC);
1998-09-15 04:19:49 +04:00
// If the list bullet frame should be positioned inside then add
// it to the flow now.
const nsStyleList* styleList;
GetStyleData(eStyleStruct_List, (const nsStyleStruct*&) styleList);
if (NS_STYLE_LIST_STYLE_POSITION_INSIDE ==
styleList->mListStylePosition) {
AddFrames(aPresContext, mBullet, nsnull);
mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
else {
mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
1998-09-15 04:19:49 +04:00
}
}
return NS_OK;
1998-09-15 04:19:49 +04:00
}
1999-07-14 21:26:20 +04:00
PRBool
nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame)
{
const nsStyleContent* styleContent;
aFrame->GetStyleData(eStyleStruct_Content,
(const nsStyleStruct*&) styleContent);
if (0 != styleContent->CounterResetCount()) {
// Winner
return PR_TRUE;
}
return PR_FALSE;
}
void
nsBlockFrame::RenumberLists(nsIPresContext* aPresContext)
1999-07-14 21:26:20 +04:00
{
if (!FrameStartsCounterScope(this)) {
// If this frame doesn't start a counter scope then we don't need
// to renumber child list items.
return;
}
// Setup initial list ordinal value
// XXX Map html's start property to counter-reset style
PRInt32 ordinal = 1;
nsIHTMLContent* hc;
if (mContent && (NS_OK == mContent->QueryInterface(kIHTMLContentIID, (void**) &hc))) {
nsHTMLValue value;
if (NS_CONTENT_ATTR_HAS_VALUE ==
hc->GetHTMLAttribute(nsHTMLAtoms::start, value)) {
if (eHTMLUnit_Integer == value.GetUnit()) {
ordinal = value.GetIntValue();
if (ordinal <= 0) {
ordinal = 1;
}
}
}
NS_RELEASE(hc);
}
// Get to first-in-flow
1999-09-16 23:56:36 +04:00
nsBlockFrame* block = (nsBlockFrame*) GetFirstInFlow();
RenumberListsInBlock(aPresContext, block, &ordinal, 0);
1999-07-14 21:26:20 +04:00
}
PRBool
nsBlockFrame::RenumberListsInBlock(nsIPresContext* aPresContext,
nsBlockFrame* aBlockFrame,
PRInt32* aOrdinal,
PRInt32 aDepth)
{
PRBool renumberedABullet = PR_FALSE;
while (nsnull != aBlockFrame) {
// Examine each line in the block
nsLineBox* line = aBlockFrame->mLines;
while (line) {
nsIFrame* kid = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
PRBool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal, aDepth);
if (kidRenumberedABullet) {
line->MarkDirty();
renumberedABullet = PR_TRUE;
}
kid->GetNextSibling(&kid);
}
line = line->mNext;
}
// Advance to the next continuation
aBlockFrame->GetNextInFlow((nsIFrame**) &aBlockFrame);
}
return renumberedABullet;
}
// XXX temporary code: after ib work is done in frame construction
// code this can be removed.
PRBool
nsBlockFrame::RenumberListsIn(nsIPresContext* aPresContext,
nsIFrame* aContainerFrame,
PRInt32* aOrdinal,
PRInt32 aDepth)
1999-07-14 21:26:20 +04:00
{
PRBool renumberedABullet = PR_FALSE;
1999-07-14 21:26:20 +04:00
// For each flow-block...
while (nsnull != aContainerFrame) {
// For each frame in the flow-block...
nsIFrame* kid;
aContainerFrame->FirstChild(aPresContext, nsnull, &kid);
1999-07-14 21:26:20 +04:00
while (nsnull != kid) {
PRBool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal, aDepth);
if (kidRenumberedABullet) {
renumberedABullet = PR_TRUE;
}
kid->GetNextSibling(&kid);
}
aContainerFrame->GetNextInFlow(&aContainerFrame);
}
return renumberedABullet;
}
PRBool
nsBlockFrame::RenumberListsFor(nsIPresContext* aPresContext,
nsIFrame* aKid,
PRInt32* aOrdinal,
PRInt32 aDepth)
{
// add in a sanity check for absurdly deep frame trees. See bug 42138
if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth)
return PR_FALSE;
PRBool kidRenumberedABullet = PR_FALSE;
1999-07-14 21:26:20 +04:00
// If the frame is a list-item and the frame implements our
// block frame API then get it's bullet and set the list item
// ordinal.
const nsStyleDisplay* display;
aKid->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&) display);
if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) {
// Make certain that the frame isa block-frame in case
// something foreign has crept in.
nsBlockFrame* listItem;
nsresult rv = aKid->QueryInterface(kBlockFrameCID, (void**)&listItem);
if (NS_SUCCEEDED(rv)) {
if (nsnull != listItem->mBullet) {
PRBool changed;
*aOrdinal = listItem->mBullet->SetListItemOrdinal(*aOrdinal,
&changed);
if (changed) {
kidRenumberedABullet = PR_TRUE;
1999-07-14 21:26:20 +04:00
}
}
// XXX temporary? if the list-item has child list-items they
// should be numbered too; especially since the list-item is
// itself (ASSUMED!) not to be a counter-reseter.
PRBool meToo = RenumberListsInBlock(aPresContext, listItem, aOrdinal, aDepth + 1);
if (meToo) {
kidRenumberedABullet = PR_TRUE;
1999-07-14 21:26:20 +04:00
}
}
}
else if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) {
if (FrameStartsCounterScope(aKid)) {
// Don't bother recursing into a block frame that is a new
// counter scope. Any list-items in there will be handled by
// it.
}
else {
// If the display=block element ISA block-frame then go
// ahead and recurse into it as it might have child
// list-items.
nsBlockFrame* kidBlock;
nsresult rv = aKid->QueryInterface(kBlockFrameCID, (void**) &kidBlock);
if (NS_SUCCEEDED(rv)) {
kidRenumberedABullet = RenumberListsInBlock(aPresContext, kidBlock, aOrdinal, aDepth + 1);
}
}
} else if (NS_STYLE_DISPLAY_INLINE == display->mDisplay) {
// XXX temporary code: after ib work is done in frame construction
// code this can be removed.
// If the display=inline element ISA nsInlineFrame then go
// ahead and recurse into it as it might have child
// list-items.
nsInlineFrame* kidInline;
nsresult rv = aKid->QueryInterface(nsInlineFrame::kInlineFrameCID,
(void**) &kidInline);
if (NS_SUCCEEDED(rv)) {
kidRenumberedABullet = RenumberListsIn(aPresContext, aKid, aOrdinal, aDepth + 1);
}
}
return kidRenumberedABullet;
1999-07-14 21:26:20 +04:00
}
void
nsBlockFrame::ReflowBullet(nsBlockReflowState& aState,
nsHTMLReflowMetrics& aMetrics)
1998-09-15 04:19:49 +04:00
{
// Reflow the bullet now
nsSize availSize;
availSize.width = NS_UNCONSTRAINEDSIZE;
availSize.height = NS_UNCONSTRAINEDSIZE;
nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState,
mBullet, availSize);
nsReflowStatus status;
mBullet->WillReflow(aState.mPresContext);
mBullet->Reflow(aState.mPresContext, aMetrics, reflowState, status);
// Place the bullet now; use its right margin to distance it
// from the rest of the frames in the line
nscoord x = - reflowState.mComputedMargin.right - aMetrics.width;
// Approximate the bullets position; vertical alignment will provide
// the final vertical location.
1999-07-14 21:26:20 +04:00
const nsMargin& bp = aState.BorderPadding();
nscoord y = bp.top;
mBullet->SetRect(aState.mPresContext, nsRect(x, y, aMetrics.width, aMetrics.height));
mBullet->DidReflow(aState.mPresContext, NS_FRAME_REFLOW_FINISHED);
}
1998-09-15 04:19:49 +04:00
//XXX get rid of this -- its slow
void
nsBlockFrame::BuildFloaterList()
{
nsIFrame* head = nsnull;
nsIFrame* current = nsnull;
nsLineBox* line = mLines;
while (nsnull != line) {
if (line->HasFloaters()) {
nsFloaterCache* fc = line->GetFirstFloater();
while (fc) {
nsIFrame* floater = fc->mPlaceholder->GetOutOfFlowFrame();
if (nsnull == head) {
current = head = floater;
}
else {
current->SetNextSibling(floater);
current = floater;
}
fc = fc->Next();
}
}
line = line->mNext;
}
1998-09-15 04:19:49 +04:00
// Terminate end of floater list just in case a floater was removed
if (nsnull != current) {
current->SetNextSibling(nsnull);
}
mFloaters.SetFrames(head);
}
// XXX keep the text-run data in the first-in-flow of the block
#ifdef DEBUG
void
nsBlockFrame::VerifyLines(PRBool aFinalCheckOK)
{
if (!gVerifyLines) {
return;
}
nsLineBox* line = mLines;
if (!line) {
return;
}
// Add up the counts on each line. Also validate that IsFirstLine is
// set properly.
PRInt32 count = 0;
PRBool seenBlock = PR_FALSE;
while (nsnull != line) {
if (aFinalCheckOK) {
NS_ABORT_IF_FALSE(line->GetChildCount(), "empty line");
if (line->IsBlock()) {
seenBlock = PR_TRUE;
}
if (line->IsBlock()) {
NS_ASSERTION(1 == line->GetChildCount(), "bad first line");
}
}
count += line->GetChildCount();
line = line->mNext;
}
// Then count the frames
PRInt32 frameCount = 0;
nsIFrame* frame = mLines->mFirstChild;
while (nsnull != frame) {
frameCount++;
frame->GetNextSibling(&frame);
}
NS_ASSERTION(count == frameCount, "bad line list");
// Next: test that each line has right number of frames on it
line = mLines;
nsLineBox* prevLine = nsnull;
while (nsnull != line) {
count = line->GetChildCount();
frame = line->mFirstChild;
while (--count >= 0) {
frame->GetNextSibling(&frame);
}
prevLine = line;
line = line->mNext;
if ((nsnull != line) && (0 != line->GetChildCount())) {
NS_ASSERTION(frame == line->mFirstChild, "bad line list");
}
}
}
// Its possible that a frame can have some frames on an overflow
// list. But its never possible for multiple frames to have overflow
// lists. Check that this fact is actually true.
void
nsBlockFrame::VerifyOverflowSituation(nsIPresContext* aPresContext)
{
nsBlockFrame* flow = (nsBlockFrame*) GetFirstInFlow();
while (nsnull != flow) {
nsLineBox* overflowLines = GetOverflowLines(aPresContext, PR_FALSE);
if (nsnull != overflowLines) {
NS_ASSERTION(nsnull != overflowLines->mFirstChild,
"bad overflow list");
}
flow = (nsBlockFrame*) flow->mNextInFlow;
}
}
PRInt32
nsBlockFrame::GetDepth() const
{
PRInt32 depth = 0;
nsIFrame* parent = mParent;
while (nsnull != parent) {
parent->GetParent(&parent);
depth++;
}
return depth;
}
#endif