зеркало из https://github.com/mozilla/gecko-dev.git
6760 строки
216 KiB
C++
6760 строки
216 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape Communications
|
|
* Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
*/
|
|
#include "nsCOMPtr.h"
|
|
#include "nsBlockFrame.h"
|
|
#include "nsBlockReflowContext.h"
|
|
#include "nsBlockBandData.h"
|
|
#include "nsBulletFrame.h"
|
|
#include "nsLineBox.h"
|
|
#include "nsInlineFrame.h"
|
|
#include "nsLineLayout.h"
|
|
#include "nsPlaceholderFrame.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsHTMLIIDs.h"
|
|
#include "nsCSSRendering.h"
|
|
#include "nsIFrameManager.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsIReflowCommand.h"
|
|
#include "nsISpaceManager.h"
|
|
#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"
|
|
#include "nsStyleChangeList.h"
|
|
#include "nsISizeOfHandler.h"
|
|
#include "nsIFocusTracker.h"
|
|
#include "nsIFrameSelection.h"
|
|
#include "nsSpaceManager.h"
|
|
#include "prenv.h"
|
|
#include "plstr.h"
|
|
|
|
// XXX HTML:P's that are empty yet have style indicating they should
|
|
// clear floaters - we need to ignore the clear behavior.
|
|
|
|
#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;
|
|
static PRBool gListTextRuns;
|
|
|
|
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 },
|
|
{ "list-text-runs", &gListTextRuns },
|
|
};
|
|
#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
|
|
#undef REALLY_NOISY_FIRST_LINE
|
|
#undef NOISY_FIRST_LETTER
|
|
#undef NOISY_MAX_ELEMENT_SIZE
|
|
#undef NOISY_FLOATER_CLEARING
|
|
#undef NOISY_FINAL_SIZE
|
|
#undef NOISY_REMOVE_FRAME
|
|
#undef NOISY_COMBINED_AREA
|
|
#undef NOISY_VERTICAL_MARGINS
|
|
#undef NOISY_REFLOW_REASON
|
|
#undef REFLOW_STATUS_COVERAGE
|
|
#undef NOISY_SPACEMANAGER
|
|
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// 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;
|
|
}
|
|
|
|
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)) {
|
|
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) {
|
|
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() {
|
|
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;
|
|
|
|
PRPackedBool mUnconstrainedWidth;
|
|
PRPackedBool mUnconstrainedHeight;
|
|
PRPackedBool mShrinkWrapWidth;
|
|
PRPackedBool mNeedResizeReflow;
|
|
PRPackedBool mIsInlineIncrReflow;
|
|
|
|
// 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;
|
|
|
|
// Our wrapping behavior
|
|
PRPackedBool mNoWrap;
|
|
|
|
// Is this frame a root for top/bottom margin collapsing?
|
|
PRPackedBool mIsTopMarginRoot, mIsBottomMarginRoot;
|
|
|
|
// See ShouldApplyTopMargin
|
|
PRPackedBool mApplyTopMargin;
|
|
|
|
//----------------------------------------
|
|
|
|
// 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;
|
|
|
|
PRPackedBool mComputeMaxElementSize;
|
|
PRPackedBool mComputeMaximumWidth;
|
|
|
|
nsSize mMaxElementSize;
|
|
nscoord mMaximumWidth;
|
|
|
|
nscoord mMinLineHeight;
|
|
|
|
PRInt32 mLineNumber;
|
|
};
|
|
|
|
// 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);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState,
|
|
nsIPresContext* aPresContext,
|
|
nsBlockFrame* aFrame,
|
|
const nsHTMLReflowMetrics& aMetrics,
|
|
PRBool aBlockMarginRoot)
|
|
: mBlock(aFrame),
|
|
mPresContext(aPresContext),
|
|
mReflowState(aReflowState),
|
|
mIsTopMarginRoot(PR_FALSE),
|
|
mIsBottomMarginRoot(PR_FALSE),
|
|
mApplyTopMargin(PR_FALSE),
|
|
mNextRCFrame(nsnull),
|
|
mPrevBottomMargin(0),
|
|
mLineNumber(0),
|
|
mNeedResizeReflow(PR_FALSE),
|
|
mIsInlineIncrReflow(PR_FALSE)
|
|
{
|
|
const nsMargin& borderPadding = BorderPadding();
|
|
|
|
if (aBlockMarginRoot) {
|
|
mIsTopMarginRoot = PR_TRUE;
|
|
mIsBottomMarginRoot = PR_TRUE;
|
|
}
|
|
if (0 != aReflowState.mComputedBorderPadding.top) {
|
|
mIsTopMarginRoot = PR_TRUE;
|
|
}
|
|
if (0 != aReflowState.mComputedBorderPadding.bottom) {
|
|
mIsBottomMarginRoot = PR_TRUE;
|
|
}
|
|
if (mIsTopMarginRoot) {
|
|
mApplyTopMargin = 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)
|
|
mUnconstrainedWidth = PR_FALSE;
|
|
mShrinkWrapWidth = PR_FALSE;
|
|
if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedWidth) {
|
|
mContentArea.width = aReflowState.mComputedWidth;
|
|
}
|
|
else {
|
|
if (NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth) {
|
|
mContentArea.width = NS_UNCONSTRAINEDSIZE;
|
|
mUnconstrainedWidth = PR_TRUE;
|
|
}
|
|
else if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedMaxWidth) {
|
|
// Choose a width based on the content (shrink wrap width) up
|
|
// to the maximum width
|
|
mContentArea.width = aReflowState.mComputedMaxWidth;
|
|
mShrinkWrapWidth = 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).
|
|
mUnconstrainedHeight = PR_FALSE;
|
|
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.
|
|
mUnconstrainedHeight = 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:
|
|
mNoWrap = PR_TRUE;
|
|
break;
|
|
default:
|
|
mNoWrap = PR_FALSE;
|
|
break;
|
|
}
|
|
|
|
mComputeMaxElementSize = nsnull != aMetrics.maxElementSize;
|
|
mMaxElementSize.SizeTo(0, 0);
|
|
mComputeMaximumWidth = NS_REFLOW_CALC_MAX_WIDTH == (aMetrics.mFlags & NS_REFLOW_CALC_MAX_WIDTH);
|
|
mMaximumWidth = 0;
|
|
|
|
mMinLineHeight = nsHTMLReflowState::CalcLineHeight(mPresContext,
|
|
aReflowState.rendContext,
|
|
aReflowState.frame);
|
|
}
|
|
|
|
nsBlockReflowState::~nsBlockReflowState()
|
|
{
|
|
// Restore the coordinate system
|
|
const nsMargin& borderPadding = BorderPadding();
|
|
mSpaceManager->Translate(-borderPadding.left, -borderPadding.top);
|
|
}
|
|
|
|
nsLineBox*
|
|
nsBlockReflowState::NewLineBox(nsIFrame* aFrame,
|
|
PRInt32 aCount,
|
|
PRBool aIsBlock)
|
|
{
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
mPresContext->GetShell(getter_AddRefs(shell));
|
|
|
|
return NS_NewLineBox(shell, aFrame, aCount, aIsBlock);
|
|
}
|
|
|
|
void
|
|
nsBlockReflowState::FreeLineBox(nsLineBox* aLine)
|
|
{
|
|
if (aLine) {
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
mPresContext->GetShell(getter_AddRefs(presShell));
|
|
|
|
aLine->Destroy(presShell);
|
|
}
|
|
}
|
|
|
|
// Compute the amount of available space for reflowing a block frame
|
|
// at the current Y coordinate. This method assumes that
|
|
// GetAvailableSpace has already been called.
|
|
void
|
|
nsBlockReflowState::ComputeBlockAvailSpace(nsIFrame* aFrame,
|
|
nsSplittableType aSplitType,
|
|
const nsStyleDisplay* aDisplay,
|
|
nsRect& aResult)
|
|
{
|
|
aResult.y = mY;
|
|
aResult.height = mUnconstrainedHeight
|
|
? NS_UNCONSTRAINEDSIZE
|
|
: mBottomEdge - mY;
|
|
|
|
const nsMargin& borderPadding = BorderPadding();
|
|
|
|
if (NS_FRAME_SPLITTABLE_NON_RECTANGULAR == aSplitType) {
|
|
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:
|
|
// The child block will flow around the floater. Therefore
|
|
// give it all of the available space.
|
|
aResult.x = borderPadding.left;
|
|
aResult.width = mUnconstrainedWidth
|
|
? NS_UNCONSTRAINEDSIZE
|
|
: mContentArea.width;
|
|
break;
|
|
|
|
case NS_STYLE_FLOAT_EDGE_BORDER:
|
|
case NS_STYLE_FLOAT_EDGE_PADDING:
|
|
{
|
|
// The child block's border should be placed adjacent to,
|
|
// but not overlap the floater(s).
|
|
nsMargin m(0, 0, 0, 0);
|
|
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 (mUnconstrainedWidth) {
|
|
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 = mUnconstrainedWidth
|
|
? NS_UNCONSTRAINEDSIZE
|
|
: mContentArea.width;
|
|
}
|
|
}
|
|
else {
|
|
// The frame is clueless about the space manager and therefore we
|
|
// only give it free space. An example is a table frame - the
|
|
// tables do not flow around floaters.
|
|
aResult.x = mAvailSpaceRect.x + borderPadding.left;
|
|
aResult.width = mAvailSpaceRect.width;
|
|
}
|
|
}
|
|
|
|
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);
|
|
#ifdef NOISY_FLOATER_CLEARING
|
|
nsFrame::ListTag(stdout, mBlock);
|
|
printf(": ClearPastFloaters: mPrevBottomMargin=%d saveY=%d oldY=%d newY=%d deltaY=%d\n",
|
|
mPrevBottomMargin, saveY, saveY - mPrevBottomMargin, mY,
|
|
mY - saveY);
|
|
#endif
|
|
|
|
// Determine how far we just moved. If we didn't move then there
|
|
// was nothing to clear to don't mess with the normal margin
|
|
// collapsing behavior. In either case we need to restore the Y
|
|
// coordinate to what it was before the clear.
|
|
deltaY = mY - saveY;
|
|
if (0 != deltaY) {
|
|
// Pretend that the distance we just moved is a previous
|
|
// blocks bottom margin. Note that GetAvailableSpace has been
|
|
// done so that the available space calculations will be done
|
|
// after clearing the appropriate floaters.
|
|
//
|
|
// What we are doing here is applying CSS2 section 9.5.2's
|
|
// rules for clearing - "The top margin of the generated box
|
|
// is increased enough that the top border edge is below the
|
|
// bottom outer edge of the floating boxes..."
|
|
//
|
|
// What this will do is cause the top-margin of the block
|
|
// frame we are about to reflow to be collapsed with that
|
|
// distance.
|
|
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
|
|
mKidXMost = xmost;
|
|
}
|
|
if (mComputeMaxElementSize) {
|
|
UpdateMaxElementSize(nsSize(aLine->mMaxElementWidth, aLine->mBounds.height));
|
|
}
|
|
|
|
// If computing the maximum width, then update mMaximumWidth
|
|
if (mComputeMaximumWidth) {
|
|
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());
|
|
#ifdef NOISY_VERTICAL_MARGINS
|
|
nsFrame::ListTag(stdout, mBlock);
|
|
printf(": RecoverStateFrom: y=%d child ", mY);
|
|
nsFrame::ListTag(stdout, aLine->mFirstChild);
|
|
printf(" has clear of %d => %s, mPrevBottomMargin=%d\n", aLine->mBreakType,
|
|
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)) {
|
|
if (nsBlockReflowContext::IsHTMLParagraph(aLine->mFirstChild)) {
|
|
// Empty HTML paragraphs disappear entirely - their margins go
|
|
// to zero. Therefore we leave mPrevBottomMargin alone.
|
|
}
|
|
else {
|
|
// 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;
|
|
mBlock->SlideLine(*this, aLine, finalDeltaY);
|
|
|
|
// Place floaters for this line into the space manager
|
|
if (aLine->HasFloaters()) {
|
|
// Undo border/padding translation since the nsFloaterCache's
|
|
// coordinates are relative to the frame not relative to the
|
|
// border/padding.
|
|
const nsMargin& bp = BorderPadding();
|
|
mSpaceManager->Translate(-bp.left, -bp.top);
|
|
|
|
// Place the floaters into the space-manager again. Also slide
|
|
// them, just like the regular frames on the line.
|
|
nsRect r;
|
|
nsFloaterCache* fc = aLine->GetFirstFloater();
|
|
while (fc) {
|
|
fc->mRegion.y += 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();
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
nsTextRun::DeleteTextRuns(mTextRuns);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::Destroy(nsIPresContext* aPresContext)
|
|
{
|
|
// Outside bullets are not in our child-list so check for them here
|
|
// and delete them when present.
|
|
if (HaveOutsideBullet()) {
|
|
mBullet->Destroy(aPresContext);
|
|
mBullet = nsnull;
|
|
}
|
|
|
|
mFloaters.DestroyFrames(aPresContext);
|
|
|
|
nsLineBox::DeleteLineList(aPresContext, mLines);
|
|
|
|
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))) {
|
|
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;
|
|
}
|
|
return it->QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
return nsBlockFrameSuper::QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::IsSplittable(nsSplittableType& aIsSplittable) const
|
|
{
|
|
aIsSplittable = NS_FRAME_SPLITTABLE_NON_RECTANGULAR;
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void
|
|
ListTextRuns(FILE* out, PRInt32 aIndent, nsTextRun* aRuns)
|
|
{
|
|
while (nsnull != aRuns) {
|
|
aRuns->List(out, aIndent);
|
|
aRuns = aRuns->GetNext();
|
|
}
|
|
}
|
|
|
|
NS_METHOD
|
|
nsBlockFrame::List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const
|
|
{
|
|
IndentBy(out, aIndent);
|
|
ListTag(out);
|
|
nsIView* view;
|
|
GetView(aPresContext, &view);
|
|
if (nsnull != view) {
|
|
fprintf(out, " [view=%p]", view);
|
|
}
|
|
if (nsnull != mNextSibling) {
|
|
fprintf(out, " next=%p", mNextSibling);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
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++;
|
|
|
|
// Output the lines
|
|
if (nsnull != mLines) {
|
|
nsLineBox* line = mLines;
|
|
while (nsnull != line) {
|
|
line->List(aPresContext, out, aIndent);
|
|
line = line->mNext;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Output the text-runs
|
|
if (gListTextRuns && mTextRuns) {
|
|
IndentBy(out, aIndent);
|
|
fputs("text-runs <\n", out);
|
|
|
|
ListTextRuns(out, aIndent + 1, mTextRuns);
|
|
|
|
IndentBy(out, aIndent);
|
|
fputs(">\n", out);
|
|
}
|
|
|
|
aIndent--;
|
|
IndentBy(out, aIndent);
|
|
fputs(">\n", out);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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");
|
|
*aType = nsLayoutAtoms::blockFrame;
|
|
NS_ADDREF(*aType);
|
|
return NS_OK;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Child frame enumeration
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::FirstChild(nsIPresContext* aPresContext,
|
|
nsIAtom* aListName,
|
|
nsIFrame** aFirstChild) const
|
|
{
|
|
NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer");
|
|
if (nsnull == aListName) {
|
|
*aFirstChild = (nsnull != mLines) ? mLines->mFirstChild : nsnull;
|
|
return NS_OK;
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::IsPercentageBase(PRBool& aBase) const
|
|
{
|
|
aBase = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Frame structure methods
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Reflow methods
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::Reflow(nsIPresContext* aPresContext,
|
|
nsHTMLReflowMetrics& aMetrics,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsReflowStatus& aStatus)
|
|
{
|
|
#ifdef DEBUG
|
|
if (gNoisyReflow) {
|
|
IndentBy(stdout, gNoiseIndent);
|
|
ListTag(stdout);
|
|
printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
|
|
aReflowState.availableWidth, aReflowState.availableHeight,
|
|
aReflowState.mComputedWidth, aReflowState.mComputedHeight);
|
|
}
|
|
if (gNoisy) {
|
|
gNoiseIndent++;
|
|
}
|
|
PRTime start;
|
|
PRInt32 ctc;
|
|
if (gLameReflowMetrics) {
|
|
start = PR_Now();
|
|
ctc = nsLineBox::GetCtorCount();
|
|
}
|
|
#endif
|
|
|
|
if (IsFrameTreeTooDeep(aReflowState, aMetrics)) {
|
|
#ifdef DEBUG_kipp
|
|
{
|
|
extern char* nsPresShell_ReflowStackPointerTop;
|
|
char marker;
|
|
char* newsp = (char*) ▮
|
|
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();
|
|
}
|
|
|
|
nsBlockReflowState state(aReflowState, aPresContext, this, aMetrics,
|
|
NS_BLOCK_MARGIN_ROOT & mState);
|
|
|
|
if (eReflowReason_Resize != aReflowState.reason) {
|
|
RenumberLists(aPresContext);
|
|
ComputeTextRuns(aPresContext);
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
PRBool isStyleChange = PR_FALSE;
|
|
state.mIsInlineIncrReflow = 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;
|
|
|
|
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
|
|
|
|
#ifdef OLD_BLOCK_INCREMENTAL_REFLOW
|
|
// the old way...
|
|
// in the normal case, this just marks the line dirty
|
|
// but, it causes an incremental reflow targeted at a frame
|
|
// inside a continued span to get dropped on the floor. see bug 25510
|
|
// This will be yanked as soon as we have high confidence in the enabled code below
|
|
rv = PrepareChildIncrementalReflow(state);
|
|
#else
|
|
// 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()))
|
|
{
|
|
if (!isFloater) // punt if isFloater!
|
|
{
|
|
// 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.mIsInlineIncrReflow = PR_TRUE;
|
|
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.
|
|
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().
|
|
return rv;
|
|
}
|
|
}
|
|
rv = PrepareChildIncrementalReflow(state);
|
|
#endif
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_ASSERTION(0, "setting up reflow failed.\n");
|
|
return rv;
|
|
}
|
|
|
|
// Now reflow...
|
|
rv = ReflowDirtyLines(state);
|
|
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
|
|
}
|
|
}
|
|
|
|
// XXX_pref get rid of this!
|
|
BuildFloaterList();
|
|
|
|
// Compute our final size
|
|
ComputeFinalSize(aReflowState, state, aMetrics);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
reflowState.mSpaceManager = oldSpaceManager;
|
|
}
|
|
|
|
#if 0
|
|
#ifdef NOISY_SPACEMANAGER
|
|
if (eReflowReason_Incremental == aReflowState.reason) {
|
|
if (mSpaceManager) {
|
|
ListTag(stdout);
|
|
printf(": space-manager after reflow\n");
|
|
mSpaceManager->List(stdout);
|
|
}
|
|
}
|
|
#endif
|
|
#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
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
Invalidate(aPresContext, damageRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
#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
|
|
return rv;
|
|
}
|
|
|
|
static PRBool
|
|
HaveAutoWidth(const nsHTMLReflowState& aReflowState)
|
|
{
|
|
const nsHTMLReflowState* rs = &aReflowState;
|
|
if (NS_UNCONSTRAINEDSIZE == rs->mComputedWidth) {
|
|
return PR_TRUE;
|
|
}
|
|
const nsStylePosition* pos = rs->mStylePosition;
|
|
|
|
for (;;) {
|
|
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*)
|
|
rs->parentReflowState;
|
|
if (!prs) {
|
|
return PR_TRUE;
|
|
}
|
|
rs = prs;
|
|
pos = prs->mStylePosition;
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
static PRBool
|
|
IsPercentageUnitSides(const nsStyleSides* aSides)
|
|
{
|
|
return eStyleUnit_Percent == aSides->GetLeftUnit()
|
|
|| eStyleUnit_Percent == aSides->GetRightUnit()
|
|
|| eStyleUnit_Percent == aSides->GetTopUnit()
|
|
|| eStyleUnit_Percent == aSides->GetBottomUnit();
|
|
}
|
|
|
|
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 (IsPercentageUnitSides(&space->mMargin)
|
|
|| IsPercentageUnitSides(&space->mPadding)
|
|
|| 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()
|
|
|| IsPercentageUnitSides(&pos->mOffset)) { // XXX need more here!!!
|
|
return PR_TRUE;
|
|
}
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState,
|
|
nsBlockReflowState& aState,
|
|
nsHTMLReflowMetrics& aMetrics)
|
|
{
|
|
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.mIsBottomMarginRoot ? "yes" : "no",
|
|
aState.mPrevBottomMargin,
|
|
borderPadding.top, borderPadding.bottom);
|
|
#endif
|
|
|
|
// Special check for zero sized content: If our content is zero
|
|
// sized then we collapse into nothingness.
|
|
//
|
|
// Consensus after discussion with a few CSS folks is that html's
|
|
// notion of collapsing <P>'s should take precedence over non
|
|
// auto-sided block elements. Therefore we don't honor the width,
|
|
// height, border or padding attributes (the parent has to not apply
|
|
// a margin for us also).
|
|
//
|
|
// Note that this is <b>only</b> done for html paragraphs. Its not
|
|
// appropriate to apply it to other containers, especially XML
|
|
// content!
|
|
PRBool isHTMLParagraph = 0 != (mState & NS_BLOCK_IS_HTML_PARAGRAPH);
|
|
if (isHTMLParagraph &&
|
|
(aReflowState.mStyleDisplay->mDisplay == NS_STYLE_DISPLAY_BLOCK) &&
|
|
(((0 == aState.mKidXMost) ||
|
|
(0 == aState.mKidXMost - borderPadding.left)) &&
|
|
(0 == aState.mY - borderPadding.top))) {
|
|
// Zero out most everything
|
|
aMetrics.width = 0;
|
|
aMetrics.height = 0;
|
|
aMetrics.ascent = 0;
|
|
aMetrics.descent = 0;
|
|
aMetrics.mCarriedOutBottomMargin = 0;
|
|
|
|
// Note: Don't zero out the max-element-sizes: they will be zero
|
|
// if this is truly empty, otherwise they won't because of a
|
|
// floater.
|
|
if (nsnull != aMetrics.maxElementSize) {
|
|
aMetrics.maxElementSize->width = aState.mMaxElementSize.width;
|
|
aMetrics.maxElementSize->height = aState.mMaxElementSize.height;
|
|
}
|
|
}
|
|
else {
|
|
// Compute final width
|
|
nscoord maxWidth = 0, maxHeight = 0;
|
|
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;
|
|
}
|
|
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.mUnconstrainedWidth && !aState.mShrinkWrapWidth &&
|
|
!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.mComputeMaxElementSize) {
|
|
// Adjust the computedWidth
|
|
if (aState.mNoWrap) {
|
|
// 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;
|
|
}
|
|
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.mShrinkWrapWidth && aState.mNeedResizeReflow) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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.mShrinkWrapWidth) {
|
|
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.mIsBottomMarginRoot) {
|
|
if (aState.mY + aState.mPrevBottomMargin != aMetrics.height) {
|
|
aState.mPrevBottomMargin = 0;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
nscoord autoHeight = aState.mY;
|
|
|
|
// Shrink wrap our height around our contents.
|
|
if (aState.mIsBottomMarginRoot) {
|
|
// 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.mComputeMaxElementSize) {
|
|
maxHeight = aState.mMaxElementSize.height +
|
|
borderPadding.top + borderPadding.bottom;
|
|
}
|
|
}
|
|
|
|
aMetrics.ascent = aMetrics.height;
|
|
aMetrics.descent = 0;
|
|
if (aState.mComputeMaxElementSize) {
|
|
// Store away the final value
|
|
aMetrics.maxElementSize->width = maxWidth;
|
|
aMetrics.maxElementSize->height = maxHeight;
|
|
}
|
|
|
|
// Return bottom margin information
|
|
aMetrics.mCarriedOutBottomMargin =
|
|
aState.mIsBottomMarginRoot ? 0 : aState.mPrevBottomMargin;
|
|
|
|
#ifdef DEBUG
|
|
if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) {
|
|
ListTag(stdout);
|
|
printf(": WARNING: desired:%d,%d\n", aMetrics.width, aMetrics.height);
|
|
}
|
|
if (aState.mComputeMaxElementSize &&
|
|
((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.mComputeMaxElementSize) {
|
|
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
|
|
}
|
|
|
|
// If we're requested to update our maximum width, then compute it
|
|
if (aState.mComputeMaximumWidth) {
|
|
// We need to add in for the right border/padding
|
|
aMetrics.mMaximumWidth = aState.mMaximumWidth + borderPadding.right;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
#ifdef NOISY_COMBINED_AREA
|
|
ListTag(stdout);
|
|
printf(": ca=%d,%d,%d,%d\n", xa, ya, xb-xa, yb-ya);
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::PrepareInitialReflow(nsBlockReflowState& aState)
|
|
{
|
|
PrepareResizeReflow(aState);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::PrepareChildIncrementalReflow(nsBlockReflowState& aState)
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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);
|
|
|
|
// 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;
|
|
|
|
// See if this is this a constrained resize reflow
|
|
if ((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) {
|
|
// The line's bounds are relative to the border edge of the frame
|
|
nscoord newAvailWidth = aState.mReflowState.mComputedBorderPadding.left;
|
|
|
|
if (NS_SHRINKWRAPWIDTH == aState.mReflowState.mComputedWidth) {
|
|
newAvailWidth += aState.mReflowState.mComputedMaxWidth;
|
|
} else {
|
|
newAvailWidth += aState.mReflowState.mComputedWidth;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (gNoisyReflow) {
|
|
IndentBy(stdout, gNoiseIndent);
|
|
ListTag(stdout);
|
|
printf(": trying to avoid marking all lines dirty\n");
|
|
}
|
|
#endif
|
|
|
|
PRBool notWrapping = aState.mNoWrap;
|
|
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.
|
|
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->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.mNoWrap ? "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;
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::RecoverStateFrom(nsBlockReflowState& aState,
|
|
nsLineBox* aLine,
|
|
nscoord aDeltaY,
|
|
nsRect* aDamageRect)
|
|
{
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Reflow the dirty lines
|
|
*/
|
|
nsresult
|
|
nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
PRBool keepGoing = PR_TRUE;
|
|
|
|
#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",
|
|
kReflowCommandType[type], type,
|
|
aState.mIsInlineIncrReflow ? "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;
|
|
|
|
nscoord deltaY = 0;
|
|
|
|
// Reflow the lines that are already ours
|
|
aState.mPrevLine = nsnull;
|
|
nsLineBox* line = mLines;
|
|
if (aState.mIsInlineIncrReflow && aState.mNextRCFrame)
|
|
{
|
|
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()) {
|
|
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.mComputeMaximumWidth && ::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 have
|
|
// it invalidate the dirty area
|
|
rv = ReflowLine(aState, line, &keepGoing, incrementalReflow);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
if (!(NS_FRAME_IS_COMPLETE(aState.mReflowStatus)))
|
|
{
|
|
printf("line %p is not complete\n", line);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
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()) {
|
|
Invalidate(aState.mPresContext, damageRect);
|
|
}
|
|
}
|
|
|
|
#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
|
|
|
|
// If this is an inline frame then its time to stop
|
|
aState.mPrevLine = line;
|
|
line = line->mNext;
|
|
aState.AdvanceToNextLine();
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
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);
|
|
}
|
|
|
|
// 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 ?
|
|
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();
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (gNoisyReflow) {
|
|
gNoiseIndent--;
|
|
IndentBy(stdout, gNoiseIndent);
|
|
ListTag(stdout);
|
|
printf(": done reflowing dirty lines (status=%x)\n",
|
|
aState.mReflowStatus);
|
|
}
|
|
#endif
|
|
|
|
return rv;
|
|
}
|
|
|
|
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. aLineReflowStatus indicates
|
|
* whether or not the caller should continue to reflow more lines.
|
|
*/
|
|
nsresult
|
|
nsBlockFrame::ReflowLine(nsBlockReflowState& aState,
|
|
nsLineBox* aLine,
|
|
PRBool* aKeepReflowGoing,
|
|
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
|
|
if (aDamageDirtyArea) {
|
|
nsRect lineCombinedArea;
|
|
aLine->GetCombinedArea(&lineCombinedArea);
|
|
if ((oldCombinedArea.x != lineCombinedArea.x) ||
|
|
(oldCombinedArea.y != lineCombinedArea.y)) {
|
|
// The block has moved, and so do be safe we need to repaint
|
|
// XXX We need to improve on this...
|
|
nsRect dirtyRect;
|
|
dirtyRect.UnionRect(oldCombinedArea, lineCombinedArea);
|
|
Invalidate(aState.mPresContext, dirtyRect);
|
|
|
|
} else {
|
|
if (oldCombinedArea.width != lineCombinedArea.width) {
|
|
nsRect dirtyRect;
|
|
|
|
// Just damage the vertical strip that was either added or went
|
|
// away
|
|
dirtyRect.x = PR_MIN(oldCombinedArea.XMost(),
|
|
lineCombinedArea.XMost());
|
|
dirtyRect.y = lineCombinedArea.y;
|
|
dirtyRect.width = PR_MAX(oldCombinedArea.XMost(),
|
|
lineCombinedArea.XMost()) -
|
|
dirtyRect.x;
|
|
dirtyRect.height = PR_MAX(oldCombinedArea.height,
|
|
lineCombinedArea.height);
|
|
Invalidate(aState.mPresContext, dirtyRect);
|
|
}
|
|
if (oldCombinedArea.height != lineCombinedArea.height) {
|
|
nsRect dirtyRect;
|
|
|
|
// Just damage the horizontal strip that was either added or went
|
|
// away
|
|
dirtyRect.x = lineCombinedArea.x;
|
|
dirtyRect.y = PR_MIN(oldCombinedArea.YMost(),
|
|
lineCombinedArea.YMost());
|
|
dirtyRect.width = PR_MAX(oldCombinedArea.width,
|
|
lineCombinedArea.width);
|
|
dirtyRect.height = PR_MAX(oldCombinedArea.YMost(),
|
|
lineCombinedArea.YMost()) -
|
|
dirtyRect.y;
|
|
Invalidate(aState.mPresContext, dirtyRect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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.mComputeMaximumWidth && isBeginningLine) {
|
|
nscoord oldY = aState.mY;
|
|
nscoord oldPrevBottomMargin = aState.mPrevBottomMargin;
|
|
PRBool oldUnconstrainedWidth = aState.mUnconstrainedWidth;
|
|
|
|
// 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.mUnconstrainedWidth = PR_TRUE;
|
|
ReflowInlineFrames(aState, aLine, aKeepReflowGoing, PR_TRUE);
|
|
aState.mY = oldY;
|
|
aState.mPrevBottomMargin = oldPrevBottomMargin;
|
|
aState.mUnconstrainedWidth = oldUnconstrainedWidth;
|
|
|
|
// Update the line's maximum width
|
|
aLine->mMaximumWidth = aLine->mBounds.XMost();
|
|
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.mComputeMaxElementSize;
|
|
nscoord oldComputeMaximumWidth = aState.mComputeMaximumWidth;
|
|
|
|
aState.mComputeMaxElementSize = PR_FALSE;
|
|
aState.mComputeMaximumWidth = PR_FALSE;
|
|
rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
|
|
aState.mComputeMaxElementSize = oldComputeMaxElementSize;
|
|
aState.mComputeMaximumWidth = oldComputeMaximumWidth;
|
|
|
|
} else {
|
|
rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
|
|
}
|
|
|
|
// We don't really know what changed in the line, so use the union
|
|
// of the old and new combined areas
|
|
if (aDamageDirtyArea) {
|
|
nsRect combinedArea;
|
|
aLine->GetCombinedArea(&combinedArea);
|
|
|
|
nsRect dirtyRect;
|
|
dirtyRect.UnionRect(oldCombinedArea, combinedArea);
|
|
Invalidate(aState.mPresContext, dirtyRect);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
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,
|
|
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,
|
|
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,
|
|
nsIFrame*& aFrameResult,
|
|
PRBool& aStopPulling)
|
|
{
|
|
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
|
|
*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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::SlideLine(nsBlockReflowState& aState,
|
|
nsLineBox* aLine, nscoord aDY)
|
|
{
|
|
// 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);
|
|
if (aDY) {
|
|
r.y += aDY;
|
|
kid->SetRect(aState.mPresContext, r);
|
|
}
|
|
|
|
// 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)) {
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
line = line->mNext;
|
|
}
|
|
|
|
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;
|
|
}
|
|
return followingBlockFrame;
|
|
}
|
|
|
|
PRBool
|
|
nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState,
|
|
nsLineBox* aLine)
|
|
{
|
|
if (aState.mApplyTopMargin) {
|
|
// 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.mApplyTopMargin = 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.mApplyTopMargin = PR_TRUE;
|
|
return PR_TRUE;
|
|
}
|
|
else if (line->HasFloaters()) {
|
|
// A line which preceeds aLine is not empty therefore the top
|
|
// margin applies.
|
|
aState.mApplyTopMargin = PR_TRUE;
|
|
return PR_TRUE;
|
|
}
|
|
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)) {
|
|
return nsnull;
|
|
}
|
|
PRBool isws = PR_FALSE;
|
|
tc->IsOnlyWhitespace(&isws);
|
|
NS_RELEASE(tc);
|
|
if (!isws) {
|
|
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");
|
|
|
|
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.mComputeMaxElementSize,
|
|
aState.mComputeMaximumWidth);
|
|
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;
|
|
}
|
|
#ifdef NOISY_VERTICAL_MARGINS
|
|
ListTag(stdout);
|
|
printf(": y=%d child ", aState.mY);
|
|
ListTag(stdout, frame);
|
|
printf(" has clear of %d => %s, mPrevBottomMargin=%d\n",
|
|
breakType,
|
|
applyTopMargin ? "applyTopMargin" : "nope",
|
|
aState.mPrevBottomMargin);
|
|
#endif
|
|
}
|
|
|
|
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();
|
|
aLine->SetLineIsImpactedByFloater(aState.IsImpactedByFloater());
|
|
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
|
|
nsReflowStatus frameReflowStatus=NS_FRAME_COMPLETE;
|
|
nsMargin computedOffsets;
|
|
rv = brc.ReflowBlock(frame, availSpace,
|
|
applyTopMargin, aState.mPrevBottomMargin,
|
|
aState.IsAdjacentWithTop(),
|
|
computedOffsets, frameReflowStatus);
|
|
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;
|
|
nsRect combinedArea(0,0,0,0);
|
|
*aKeepReflowGoing = brc.PlaceBlock(isAdjacentWithTop, computedOffsets,
|
|
&collapsedBottomMargin,
|
|
aLine->mBounds, combinedArea);
|
|
|
|
if (aState.mShrinkWrapWidth) {
|
|
// 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.mNeedResizeReflow = PR_TRUE;
|
|
}
|
|
if (aState.mUnconstrainedWidth || aState.mShrinkWrapWidth) {
|
|
// 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
|
|
if (collapsedBottomMargin >= 0) {
|
|
aState.mPrevBottomMargin = collapsedBottomMargin;
|
|
}
|
|
else {
|
|
// Leave margin alone: it was a collapsed paragraph that
|
|
// must not interfere with the running margin calculations
|
|
// (in other words it should act like an empty line of
|
|
// whitespace).
|
|
}
|
|
}
|
|
#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.mComputeMaxElementSize) {
|
|
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.mComputeMaximumWidth) {
|
|
aLine->mMaximumWidth = brc.GetMaximumWidth();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
#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 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);
|
|
}
|
|
else {
|
|
rv = DoReflowInlineFramesAuto(aState, aLine, aKeepReflowGoing,
|
|
&lineReflowStatus,
|
|
aUpdateMaximumWidth);
|
|
}
|
|
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)
|
|
{
|
|
nsLineLayout* ll = new nsLineLayout(aState.mPresContext,
|
|
aState.mReflowState.mSpaceManager,
|
|
&aState.mReflowState,
|
|
aState.mComputeMaxElementSize);
|
|
if (!ll) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
ll->Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
|
|
ll->SetReflowTextRuns(mTextRuns);
|
|
nsresult rv = DoReflowInlineFrames(aState, *ll, aLine, aKeepReflowGoing,
|
|
aLineReflowStatus, aUpdateMaximumWidth);
|
|
ll->EndLineReflow();
|
|
delete ll;
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::DoReflowInlineFramesAuto(nsBlockReflowState& aState,
|
|
nsLineBox* aLine,
|
|
PRBool* aKeepReflowGoing,
|
|
PRUint8* aLineReflowStatus,
|
|
PRBool aUpdateMaximumWidth)
|
|
{
|
|
nsLineLayout lineLayout(aState.mPresContext,
|
|
aState.mReflowState.mSpaceManager,
|
|
&aState.mReflowState,
|
|
aState.mComputeMaxElementSize);
|
|
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
|
|
lineLayout.SetReflowTextRuns(mTextRuns);
|
|
nsresult rv = DoReflowInlineFrames(aState, lineLayout, aLine,
|
|
aKeepReflowGoing, aLineReflowStatus,
|
|
aUpdateMaximumWidth);
|
|
lineLayout.EndLineReflow();
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
|
|
nsLineLayout& aLineLayout,
|
|
nsLineBox* aLine,
|
|
PRBool* aKeepReflowGoing,
|
|
PRUint8* aLineReflowStatus,
|
|
PRBool aUpdateMaximumWidth)
|
|
{
|
|
// 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();
|
|
aLine->SetLineIsImpactedByFloater(impactedByFloaters);
|
|
|
|
const nsMargin& borderPadding = aState.BorderPadding();
|
|
nscoord x = aState.mAvailSpaceRect.x + borderPadding.left;
|
|
nscoord availWidth = aState.mAvailSpaceRect.width;
|
|
nscoord availHeight;
|
|
if (aState.mUnconstrainedHeight) {
|
|
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
|
|
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, 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();
|
|
#ifdef NOISY_FIRST_LETTER
|
|
ListTag(stdout);
|
|
printf(": reflowing ");
|
|
nsFrame::ListTag(stdout, aFrame);
|
|
printf(" reflowingFirstLetter=%s\n", reflowingFirstLetter ? "on" : "off");
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
#ifdef REALLY_NOISY_REFLOW_CHILD
|
|
nsFrame::ListTag(stdout, aFrame);
|
|
printf(": status=%x\n", frameReflowStatus);
|
|
#endif
|
|
|
|
#if defined(REFLOW_STATUS_COVERAGE)
|
|
RecordReflowStatus(PR_FALSE, frameReflowStatus);
|
|
#endif
|
|
|
|
// Send post-reflow notification
|
|
aState.mPrevChild = aFrame;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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
|
|
|
|
// 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);
|
|
|
|
// 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())) {
|
|
splitLine = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
nsresult
|
|
nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState,
|
|
nsLineBox* aLine,
|
|
nsIFrame* aFrame,
|
|
PRBool& aMadeNewFrame)
|
|
{
|
|
aMadeNewFrame = PR_FALSE;
|
|
nsresult rv;
|
|
nsIFrame* nextInFlow;
|
|
rv = CreateNextInFlow(aState.mPresContext, this, aFrame, nextInFlow);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
if (nsnull != nextInFlow) {
|
|
aMadeNewFrame = PR_TRUE;
|
|
aLine->SetChildCount(aLine->GetChildCount() + 1);
|
|
}
|
|
#ifdef DEBUG
|
|
VerifyLines(PR_FALSE);
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::SplitLine(nsBlockReflowState& aState,
|
|
nsLineLayout& aLineLayout,
|
|
nsLineBox* aLine,
|
|
nsIFrame* aFrame)
|
|
{
|
|
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;
|
|
}
|
|
newLine->mNext = aLine->mNext;
|
|
aLine->mNext = newLine;
|
|
aLine->SetChildCount(aLine->GetChildCount() - pushCount);
|
|
#ifdef DEBUG
|
|
if (gReallyNoisyReflow) {
|
|
newLine->List(aState.mPresContext, stdout, gNoiseIndent+1);
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
|
|
PRBool
|
|
nsBlockFrame::ShouldJustifyLine(nsBlockReflowState& aState, nsLineBox* aLine)
|
|
{
|
|
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();
|
|
}
|
|
|
|
// The next line is empty, try the next one
|
|
next = next->mNext;
|
|
}
|
|
|
|
// 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,
|
|
nsLineBox* aLine,
|
|
PRBool* aKeepReflowGoing,
|
|
PRBool aUpdateMaximumWidth)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
// Trim extra white-space from the line before placing the frames
|
|
PRBool trimmed = aLineLayout.TrimTrailingWhiteSpace();
|
|
aLine->SetTrimmed(trimmed);
|
|
|
|
// 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;
|
|
aLineLayout.VerticalAlignFrames(aLine, maxElementSize);
|
|
// See if we're shrink wrapping the width
|
|
if (aState.mShrinkWrapWidth) {
|
|
// 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();
|
|
}
|
|
}
|
|
#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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Only block frames horizontally align their children because
|
|
// inline frames "shrink-wrap" around their children (therefore
|
|
// there is no extra horizontal space).
|
|
#if XXX_fix_me
|
|
PRBool allowJustify = PR_TRUE;
|
|
if (NS_STYLE_TEXT_ALIGN_JUSTIFY == aState.mStyleText->mTextAlign) {
|
|
allowJustify = ShouldJustifyLine(aState, aLine);
|
|
}
|
|
#else
|
|
PRBool allowJustify = PR_FALSE;
|
|
#endif
|
|
|
|
PRBool successful;
|
|
nsRect combinedArea;
|
|
successful = aLineLayout.HorizontalAlignFrames(aLine->mBounds, allowJustify,
|
|
aState.mShrinkWrapWidth);
|
|
if (!successful) {
|
|
// Mark the line dirty and then later once we've determined the width
|
|
// we can do the horizontal alignment
|
|
aLine->MarkDirty();
|
|
aState.mNeedResizeReflow = PR_TRUE;
|
|
}
|
|
aLineLayout.RelativePositionFrames(combinedArea);
|
|
aLine->SetCombinedArea(combinedArea);
|
|
if (addedBullet) {
|
|
aLineLayout.RemoveBulletFrame(mBullet);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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) {
|
|
NS_ASSERTION(NS_FRAME_COMPLETE == aState.mReflowStatus,
|
|
"lost reflow status");
|
|
aState.mReflowStatus = NS_FRAME_NOT_COMPLETE;
|
|
*aKeepReflowGoing = PR_FALSE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
aState.mY = newY;
|
|
if (aState.mComputeMaxElementSize) {
|
|
#ifdef NOISY_MAX_ELEMENT_SIZE
|
|
IndentBy(stdout, GetDepth());
|
|
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableWidth) {
|
|
printf("PASS1 ");
|
|
}
|
|
ListTag(stdout);
|
|
printf(": line.floaters=%s band.floaterCount=%d\n",
|
|
//aLine->mFloaters.NotEmpty() ? "yes" : "no",
|
|
aState.mHaveRightFloaters ? "(have right floaters)" : "",
|
|
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.mComputeMaxElementSize) {
|
|
aState.UpdateMaxElementSize(maxElementSize);
|
|
// We also cache the max element width in the line. This is needed for
|
|
// incremental reflow
|
|
aLine->mMaxElementWidth = maxElementSize.width;
|
|
}
|
|
|
|
} 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);
|
|
}
|
|
|
|
// 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.mUnconstrainedWidth || aState.mShrinkWrapWidth)) {
|
|
// 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.mShrinkWrapWidth) {
|
|
// Mark the line dirty so we come back and re-place the floater once
|
|
// the shrink wrap width is determined
|
|
aLine->MarkDirty();
|
|
aState.mNeedResizeReflow = 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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.mComputeMaxElementSize) {
|
|
aState.UpdateMaxElementSize(aMaxElementSize);
|
|
// We also cache the max element width in the line. This is needed for
|
|
// incremental reflow
|
|
aLine->mMaxElementWidth = aMaxElementSize.width;
|
|
}
|
|
|
|
// 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.mUnconstrainedWidth) {
|
|
aLine->mMaximumWidth = aLine->mBounds.XMost();
|
|
}
|
|
|
|
// Update xmost
|
|
nscoord xmost = aLine->mBounds.XMost();
|
|
#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.mShrinkWrapWidth && aLine->IsLineWrapped()) {
|
|
aState.mKidXMost = aState.BorderPadding().left + aState.mContentArea.width;
|
|
|
|
}
|
|
else if (xmost > aState.mKidXMost) {
|
|
aState.mKidXMost = xmost;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::PushLines(nsBlockReflowState& aState)
|
|
{
|
|
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) {
|
|
mLines = overflowLines;
|
|
}
|
|
else {
|
|
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::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::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;
|
|
}
|
|
|
|
nsresult
|
|
nsBlockFrame::AddFrames(nsIPresContext* aPresContext,
|
|
nsIFrame* aFrameList,
|
|
nsIFrame* aPrevSibling)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
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);
|
|
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::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)
|
|
{
|
|
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;
|
|
}
|
|
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
|
|
|
|
// Remove frame and all of its continuations
|
|
while (nsnull != aDeletedFrame) {
|
|
while ((nsnull != line) && (nsnull != aDeletedFrame)) {
|
|
#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();
|
|
}
|
|
|
|
// 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);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advance to next flow block if the frame has more continuations
|
|
if (nsnull != aDeletedFrame) {
|
|
flow = (nsBlockFrame*) flow->mNextInFlow;
|
|
NS_ASSERTION(nsnull != flow, "whoops, continuation without a parent");
|
|
prevLine = nsnull;
|
|
line = flow->mLines;
|
|
linep = &flow->mLines;
|
|
prevSibling = nsnull;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
VerifyLines(PR_TRUE);
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Floater support
|
|
|
|
nsresult
|
|
nsBlockFrame::ReflowFloater(nsBlockReflowState& aState,
|
|
nsPlaceholderFrame* aPlaceholder,
|
|
nsRect& aCombinedRect,
|
|
nsMargin& aMarginResult,
|
|
nsMargin& aComputedOffsetsResult)
|
|
{
|
|
// 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.mComputeMaxElementSize,
|
|
aState.mComputeMaximumWidth);
|
|
brc.SetNextRCFrame(aState.mNextRCFrame);
|
|
|
|
// Reflow the floater
|
|
nsReflowStatus frameReflowStatus;
|
|
nsresult rv = brc.ReflowBlock(floater, availSpace, PR_TRUE, 0,
|
|
isAdjacentWithTop,
|
|
aComputedOffsetsResult, frameReflowStatus);
|
|
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.mComputeMaxElementSize) {
|
|
aState.StoreMaxElementSize(floater, brc.GetMaxElementSize());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Pass on updated available space to the current inline reflow engine
|
|
GetAvailableSpace();
|
|
aLineLayout.UpdateBand(mAvailSpaceRect.x + BorderPadding().left, mY,
|
|
mUnconstrainedWidth ? NS_UNCONSTRAINEDSIZE : mAvailSpaceRect.width,
|
|
mAvailSpaceRect.height,
|
|
isLeftFloater,
|
|
aPlaceholder->GetOutOfFlowFrame());
|
|
|
|
// Restore coordinate system
|
|
mSpaceManager->Translate(dx, dy);
|
|
}
|
|
else {
|
|
// This floater will be placed after the line is done (it is a
|
|
// below-current-line floater).
|
|
mBelowCurrentLineFloaters.Append(fc);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
break;
|
|
}
|
|
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;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
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)
|
|
{
|
|
// 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();
|
|
|
|
// Get the type of floater
|
|
const nsStyleDisplay* floaterDisplay;
|
|
const nsStyleSpacing* floaterSpacing;
|
|
const nsStylePosition* floaterPosition;
|
|
floater->GetStyleData(eStyleStruct_Display,
|
|
(const nsStyleStruct*&)floaterDisplay);
|
|
floater->GetStyleData(eStyleStruct_Spacing,
|
|
(const nsStyleStruct*&)floaterSpacing);
|
|
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();
|
|
}
|
|
|
|
// 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.
|
|
while (!CanPlaceFloater(region, floaterDisplay->mFloats)) {
|
|
mY += mAvailSpaceRect.height;
|
|
GetAvailableSpace();
|
|
}
|
|
|
|
// Assign an x and y coordinate to the floater. Note that the x,y
|
|
// coordinates are computed <b>relative to the translation in the
|
|
// spacemanager</b> which means that the impacted region will be
|
|
// <b>inside</b> the border/padding area.
|
|
PRBool okToAddRectRegion = PR_TRUE;
|
|
PRBool isLeftFloater;
|
|
if (NS_STYLE_FLOAT_LEFT == floaterDisplay->mFloats) {
|
|
isLeftFloater = PR_TRUE;
|
|
region.x = mAvailSpaceRect.x;
|
|
}
|
|
else {
|
|
isLeftFloater = PR_FALSE;
|
|
region.x = mAvailSpaceRect.XMost() - region.width;
|
|
}
|
|
*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.
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Place the floater in the space manager
|
|
if (okToAddRectRegion) {
|
|
#ifdef DEBUG
|
|
nsresult rv =
|
|
#endif
|
|
mSpaceManager->AddRectRegion(floater, region);
|
|
NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "bad floater placement");
|
|
}
|
|
|
|
// Save away the floaters region in the spacemanager, after making
|
|
// it relative to the containing block's frame instead of relative
|
|
// to the spacemanager translation (which is inset by the
|
|
// border+padding).
|
|
aFloaterCache->mRegion.x = region.x + borderPadding.left;
|
|
aFloaterCache->mRegion.y = region.y + borderPadding.top;
|
|
aFloaterCache->mRegion.width = region.width;
|
|
aFloaterCache->mRegion.height = region.height;
|
|
#ifdef NOISY_SPACEMANAGER
|
|
nscoord tx, ty;
|
|
mSpaceManager->GetTranslation(tx, ty);
|
|
nsFrame::ListTag(stdout, mBlock);
|
|
printf(": PlaceFloater: AddRectRegion: txy=%d,%d (%d,%d) {%d,%d,%d,%d}\n",
|
|
tx, ty, mSpaceManagerX, mSpaceManagerY,
|
|
aFloaterCache->mRegion.x, aFloaterCache->mRegion.y,
|
|
aFloaterCache->mRegion.width, aFloaterCache->mRegion.height);
|
|
#endif
|
|
|
|
// Set the origin of the floater frame, in frame coordinates. These
|
|
// coordinates are <b>not</b> relative to the spacemanager
|
|
// translation, therefore we have to factor in our border/padding.
|
|
nscoord x = borderPadding.left + aFloaterCache->mMargins.left + region.x;
|
|
nscoord y = borderPadding.top + aFloaterCache->mMargins.top + region.y;
|
|
|
|
// If floater is relatively positioned, factor that in as well
|
|
if (NS_STYLE_POSITION_RELATIVE == floaterPosition->mPosition) {
|
|
x += aFloaterCache->mOffsets.left;
|
|
y += aFloaterCache->mOffsets.top;
|
|
}
|
|
|
|
// 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 && (mUnconstrainedWidth || mShrinkWrapWidth)) {
|
|
// 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)
|
|
{
|
|
#ifdef DEBUG
|
|
if (gNoisyReflow) {
|
|
nsFrame::IndentBy(stdout, gNoiseIndent);
|
|
printf("clear floaters: in: mY=%d aY=%d(%d)\n",
|
|
mY, aY, aY - BorderPadding().top);
|
|
}
|
|
#endif
|
|
|
|
#ifdef NOISY_FLOATER_CLEARING
|
|
printf("nsBlockReflowState::ClearFloaters: aY=%d breakType=%dn",
|
|
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
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Painting, event handling
|
|
|
|
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);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const nsStyleDisplay* disp = (const nsStyleDisplay*)
|
|
mStyleContext->GetStyleData(eStyleStruct_Display);
|
|
|
|
// If overflow is hidden then set the clip rect so that children
|
|
// don't leak out of us
|
|
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
|
|
aRenderingContext.PushState();
|
|
SetClipRect(aRenderingContext);
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
PaintChildren(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer);
|
|
|
|
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
|
|
PRBool clipState;
|
|
aRenderingContext.PopState(clipState);
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::PaintFloaters(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
const nsRect& aDirtyRect)
|
|
{
|
|
for (nsLineBox* line = mLines; nsnull != line; line = line->mNext) {
|
|
if (!line->HasFloaters()) {
|
|
continue;
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::PaintChildren(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
const nsRect& aDirtyRect,
|
|
nsFramePaintLayer aWhichLayer)
|
|
{
|
|
#ifdef DEBUG
|
|
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;
|
|
}
|
|
#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);
|
|
nsFrame::IndentBy(stdout, depth+1);
|
|
printf("draw line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
|
|
line, line->mBounds.x, line->mBounds.y,
|
|
line->mBounds.width, line->mBounds.height,
|
|
lineCombinedArea.x, lineCombinedArea.y,
|
|
lineCombinedArea.width, lineCombinedArea.height);
|
|
}
|
|
if (gLamePaintMetrics) {
|
|
drawnLines++;
|
|
}
|
|
#endif
|
|
nsIFrame* kid = line->mFirstChild;
|
|
PRInt32 n = line->GetChildCount();
|
|
while (--n >= 0) {
|
|
PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid,
|
|
aWhichLayer);
|
|
kid->GetNextSibling(&kid);
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
if (gNoisyDamageRepair &&
|
|
(NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer)) {
|
|
nsRect lineCombinedArea;
|
|
line->GetCombinedArea(&lineCombinedArea);
|
|
nsFrame::IndentBy(stdout, depth+1);
|
|
printf("skip line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
|
|
line, line->mBounds.x, line->mBounds.y,
|
|
line->mBounds.width, line->mBounds.height,
|
|
lineCombinedArea.x, lineCombinedArea.y,
|
|
lineCombinedArea.width, lineCombinedArea.height);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::HandleEvent(nsIPresContext* aPresContext,
|
|
nsGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus)
|
|
{
|
|
if (aEvent->message == NS_MOUSE_MOVE) {
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
nsresult rv = aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_SUCCEEDED(rv)){
|
|
nsCOMPtr<nsIFrameSelection> frameselection;
|
|
if (NS_SUCCEEDED(shell->GetFrameSelection(getter_AddRefs(frameselection))) && frameselection){
|
|
PRBool mouseDown = PR_FALSE;
|
|
if (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 ) {
|
|
nsresult result;
|
|
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<nsIPresShell> shell;
|
|
aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (!shell)
|
|
return NS_OK;
|
|
nsCOMPtr<nsIFocusTracker> tracker;
|
|
result = shell->QueryInterface(NS_GET_IID(nsIFocusTracker),getter_AddRefs(tracker));
|
|
|
|
result = mainframe->QueryInterface(NS_GET_IID(nsILineIterator),getter_AddRefs(it));
|
|
nsIView* parentWithView;
|
|
nsPoint origin;
|
|
|
|
while(NS_SUCCEEDED(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
|
|
nsPeekOffsetStruct pos;
|
|
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){
|
|
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)
|
|
return resultFrame->HandleEvent(aPresContext, aEvent, aEventStatus);
|
|
else
|
|
return NS_OK; //just stop it
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::GetFrameForPoint(nsIPresContext* aPresContext,
|
|
const nsPoint& aPoint,
|
|
nsIFrame** aFrame)
|
|
{
|
|
nsresult rv;
|
|
|
|
if (mFloaters.NotEmpty()) {
|
|
rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::floaterList, aFrame);
|
|
if (NS_OK == rv) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
rv = GetFrameForPointUsing(aPresContext, aPoint, nsnull, aFrame);
|
|
if (NS_OK == rv) {
|
|
return NS_OK;
|
|
}
|
|
if (nsnull != mBullet) {
|
|
rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::bulletList, aFrame);
|
|
if (NS_OK == rv) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBlockFrame::ReflowDirtyChild(nsIPresShell* aPresShell, nsIFrame* aChild)
|
|
{
|
|
// 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;
|
|
}
|
|
#ifdef DEBUG_nisheeth
|
|
NS_ASSERTION(0, "nsBlockFrame::ReflowDirtyChild: Marked all lines dirty.");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
aLines = aLines->mNext;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
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
|
|
{
|
|
// XXX rewrite this
|
|
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;
|
|
}
|
|
|
|
// Add in text-run data
|
|
nsTextRun* runs = mTextRuns;
|
|
while (runs) {
|
|
PRUint32 runSize;
|
|
runs->SizeOf(aHandler, &runSize);
|
|
aHandler->AddSize(nsLayoutAtoms::textRun, runSize);
|
|
runs = runs->GetNext();
|
|
}
|
|
|
|
*aResult = sum;
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
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);
|
|
if (nsBlockReflowContext::IsHTMLParagraph(this)) {
|
|
mState |= NS_BLOCK_IS_HTML_PARAGRAPH;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
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;
|
|
|
|
if (nsLayoutAtoms::floaterList == aListName) {
|
|
mFloaters.SetFrames(aChildList);
|
|
}
|
|
else {
|
|
|
|
// Lookup up the two pseudo style contexts
|
|
if (nsnull == mPrevInFlow) {
|
|
nsIStyleContext* firstLetterStyle = GetFirstLetterStyle(aPresContext);
|
|
if (nsnull != firstLetterStyle) {
|
|
mState |= NS_BLOCK_HAS_FIRST_LETTER_STYLE;
|
|
#ifdef NOISY_FIRST_LETTER
|
|
ListTag(stdout);
|
|
printf(": first-letter style found\n");
|
|
#endif
|
|
NS_RELEASE(firstLetterStyle);
|
|
}
|
|
}
|
|
|
|
rv = AddFrames(aPresContext, aChildList, nsnull);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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
|
|
nsBlockFrame* block = (nsBlockFrame*) GetFirstInFlow();
|
|
RenumberListsInBlock(aPresContext, block, &ordinal);
|
|
}
|
|
|
|
PRBool
|
|
nsBlockFrame::RenumberListsInBlock(nsIPresContext* aPresContext,
|
|
nsBlockFrame* aBlockFrame,
|
|
PRInt32* aOrdinal)
|
|
{
|
|
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);
|
|
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)
|
|
{
|
|
PRBool renumberedABullet = PR_FALSE;
|
|
|
|
// For each flow-block...
|
|
while (nsnull != aContainerFrame) {
|
|
// For each frame in the flow-block...
|
|
nsIFrame* kid;
|
|
aContainerFrame->FirstChild(aPresContext, nsnull, &kid);
|
|
while (nsnull != kid) {
|
|
PRBool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal);
|
|
if (kidRenumberedABullet) {
|
|
renumberedABullet = PR_TRUE;
|
|
}
|
|
kid->GetNextSibling(&kid);
|
|
}
|
|
aContainerFrame->GetNextInFlow(&aContainerFrame);
|
|
}
|
|
return renumberedABullet;
|
|
}
|
|
|
|
PRBool
|
|
nsBlockFrame::RenumberListsFor(nsIPresContext* aPresContext,
|
|
nsIFrame* aKid,
|
|
PRInt32* aOrdinal)
|
|
{
|
|
PRBool kidRenumberedABullet = PR_FALSE;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
if (meToo) {
|
|
kidRenumberedABullet = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
return kidRenumberedABullet;
|
|
}
|
|
|
|
void
|
|
nsBlockFrame::ReflowBullet(nsBlockReflowState& aState,
|
|
nsHTMLReflowMetrics& aMetrics)
|
|
{
|
|
// 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.
|
|
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);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
|
|
// 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
|
|
|
|
// XXX Switch to an interface to pass to child frames -or- do the
|
|
// grovelling directly ourselves?
|
|
nsresult
|
|
nsBlockFrame::ComputeTextRuns(nsIPresContext* aPresContext)
|
|
{
|
|
// Destroy old run information first
|
|
nsTextRun::DeleteTextRuns(mTextRuns);
|
|
mTextRuns = nsnull;
|
|
|
|
nsLineLayout textRunThingy(aPresContext);
|
|
|
|
// Ask each child to find its text runs
|
|
nsLineBox* line = mLines;
|
|
while (nsnull != line) {
|
|
if (!line->IsBlock()) {
|
|
nsIFrame* frame = line->mFirstChild;
|
|
PRInt32 n = line->GetChildCount();
|
|
while (--n >= 0) {
|
|
frame->FindTextRuns(textRunThingy);
|
|
frame->GetNextSibling(&frame);
|
|
}
|
|
}
|
|
else {
|
|
// A block frame isn't text therefore it will end an open text
|
|
// run.
|
|
textRunThingy.EndTextRun();
|
|
}
|
|
line = line->mNext;
|
|
}
|
|
textRunThingy.EndTextRun();
|
|
|
|
// Now take the text-runs away from the line layout engine.
|
|
mTextRuns = textRunThingy.TakeTextRuns();
|
|
return NS_OK;
|
|
}
|
|
|
|
#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
|