gecko/layout/generic/nsBlockFrame.cpp
2007-09-03 02:51:52 -07:00

6746 lines
235 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:cindent:ts=2:et:sw=2:
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Steve Clark <buster@netscape.com>
* Robert O'Callahan <roc+moz@cs.cmu.edu>
* L. David Baron <dbaron@dbaron.org>
* IBM Corporation
* Mats Palmgren <mats.palmgren@bredband.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* rendering object for CSS display:block and display:list-item objects,
* also used inside table cells
*/
#include "nsCOMPtr.h"
#include "nsBlockFrame.h"
#include "nsBlockReflowContext.h"
#include "nsBlockReflowState.h"
#include "nsBlockBandData.h"
#include "nsBulletFrame.h"
#include "nsLineBox.h"
#include "nsInlineFrame.h"
#include "nsLineLayout.h"
#include "nsPlaceholderFrame.h"
#include "nsStyleConsts.h"
#include "nsFrameManager.h"
#include "nsPresContext.h"
#include "nsIPresShell.h"
#include "nsStyleContext.h"
#include "nsIView.h"
#include "nsIFontMetrics.h"
#include "nsHTMLParts.h"
#include "nsGkAtoms.h"
#include "nsIDOMEvent.h"
#include "nsGenericHTMLElement.h"
#include "prprf.h"
#include "nsStyleChangeList.h"
#include "nsFrameSelection.h"
#include "nsSpaceManager.h"
#include "nsIntervalSet.h"
#include "prenv.h"
#include "plstr.h"
#include "nsGUIEvent.h"
#include "nsLayoutErrors.h"
#include "nsAutoPtr.h"
#include "nsIServiceManager.h"
#include "nsIScrollableFrame.h"
#ifdef ACCESSIBILITY
#include "nsIDOMHTMLDocument.h"
#include "nsIAccessibilityService.h"
#endif
#include "nsLayoutUtils.h"
#include "nsBoxLayoutState.h"
#include "nsDisplayList.h"
#include "nsContentErrors.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSRendering.h"
#ifdef IBMBIDI
#include "nsBidiPresUtils.h"
#endif // IBMBIDI
#include "nsIDOMHTMLBodyElement.h"
#include "nsIDOMHTMLHtmlElement.h"
static const int MIN_LINES_NEEDING_CURSOR = 20;
#ifdef DEBUG
#include "nsPrintfCString.h"
#include "nsBlockDebugFlags.h"
PRBool nsBlockFrame::gLamePaintMetrics;
PRBool nsBlockFrame::gLameReflowMetrics;
PRBool nsBlockFrame::gNoisy;
PRBool nsBlockFrame::gNoisyDamageRepair;
PRBool nsBlockFrame::gNoisyIntrinsic;
PRBool nsBlockFrame::gNoisyReflow;
PRBool nsBlockFrame::gReallyNoisyReflow;
PRBool nsBlockFrame::gNoisySpaceManager;
PRBool nsBlockFrame::gVerifyLines;
PRBool nsBlockFrame::gDisableResizeOpt;
PRInt32 nsBlockFrame::gNoiseIndent;
struct BlockDebugFlags {
const char* name;
PRBool* on;
};
static const BlockDebugFlags gFlags[] = {
{ "reflow", &nsBlockFrame::gNoisyReflow },
{ "really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow },
{ "intrinsic", &nsBlockFrame::gNoisyIntrinsic },
{ "space-manager", &nsBlockFrame::gNoisySpaceManager },
{ "verify-lines", &nsBlockFrame::gVerifyLines },
{ "damage-repair", &nsBlockFrame::gNoisyDamageRepair },
{ "lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics },
{ "lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics },
{ "disable-resize-opt", &nsBlockFrame::gDisableResizeOpt },
};
#define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
static void
ShowDebugFlags()
{
printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n");
const BlockDebugFlags* bdf = gFlags;
const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS;
for (; bdf < end; bdf++) {
printf(" %s\n", bdf->name);
}
printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
printf("names (no whitespace)\n");
}
void
nsBlockFrame::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;
const BlockDebugFlags* bdf = gFlags;
const 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();
}
}
}
}
#endif
// add in a sanity check for absurdly deep frame trees. See bug 42138
// can't just use IsFrameTreeTooDeep() because that method has side effects we don't want
#define MAX_DEPTH_FOR_LIST_RENUMBERING 200 // 200 open displayable tags is pretty unrealistic
//----------------------------------------------------------------------
// Debugging support code
#ifdef DEBUG
const char* nsBlockFrame::kReflowCommandType[] = {
"ContentChanged",
"StyleChanged",
"ReflowDirty",
"Timeout",
"UserDefined",
};
#endif
#ifdef REALLY_NOISY_FIRST_LINE
static void
DumpStyleGeneaology(nsIFrame* aFrame, const char* gap)
{
fputs(gap, stdout);
nsFrame::ListTag(stdout, aFrame);
printf(": ");
nsStyleContext* sc = aFrame->GetStyleContext();
while (nsnull != sc) {
nsStyleContext* psc;
printf("%p ", sc);
psc = sc->GetParent();
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
//----------------------------------------------------------------------
const nsIID kBlockFrameCID = NS_BLOCK_FRAME_CID;
nsIFrame*
NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, PRUint32 aFlags)
{
nsBlockFrame* it = new (aPresShell) nsBlockFrame(aContext);
if (it) {
it->SetFlags(aFlags);
}
return it;
}
nsBlockFrame::~nsBlockFrame()
{
}
void
nsBlockFrame::Destroy()
{
mAbsoluteContainer.DestroyFrames(this);
// Outside bullets are not in our child-list so check for them here
// and delete them when present.
if (mBullet && HaveOutsideBullet()) {
mBullet->Destroy();
mBullet = nsnull;
}
mFloats.DestroyFrames();
nsPresContext* presContext = PresContext();
nsLineBox::DeleteLineList(presContext, mLines);
// destroy overflow lines now
nsLineList* overflowLines = RemoveOverflowLines();
if (overflowLines) {
nsLineBox::DeleteLineList(presContext, *overflowLines);
}
{
nsAutoOOFFrameList oofs(this);
oofs.mList.DestroyFrames();
// oofs is now empty and will remove the frame list property
}
nsBlockFrameSuper::Destroy();
}
NS_IMETHODIMP
nsBlockFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
NS_PRECONDITION(aInstancePtr, "null out param");
if (aIID.Equals(kBlockFrameCID)) {
*aInstancePtr = static_cast<void*>(static_cast<nsBlockFrame*>(this));
return NS_OK;
}
if (aIID.Equals(NS_GET_IID(nsILineIterator)) ||
aIID.Equals(NS_GET_IID(nsILineIteratorNavigator))) {
nsLineIterator* it = new nsLineIterator;
if (!it) {
*aInstancePtr = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(it); // reference passed to caller
const nsStyleVisibility* visibility = GetStyleVisibility();
nsresult rv = it->Init(mLines,
visibility->mDirection == NS_STYLE_DIRECTION_RTL);
if (NS_FAILED(rv)) {
*aInstancePtr = nsnull;
NS_RELEASE(it);
return rv;
}
*aInstancePtr = static_cast<nsILineIteratorNavigator*>(it);
return NS_OK;
}
return nsBlockFrameSuper::QueryInterface(aIID, aInstancePtr);
}
nsSplittableType
nsBlockFrame::GetSplittableType() const
{
return NS_FRAME_SPLITTABLE_NON_RECTANGULAR;
}
#ifdef DEBUG
NS_METHOD
nsBlockFrame::List(FILE* out, PRInt32 aIndent) const
{
IndentBy(out, aIndent);
ListTag(out);
#ifdef DEBUG_waterson
fprintf(out, " [parent=%p]", mParent);
#endif
if (HasView()) {
fprintf(out, " [view=%p]", static_cast<void*>(GetView()));
}
if (nsnull != mNextSibling) {
fprintf(out, " next=%p", static_cast<void*>(mNextSibling));
}
// Output the flow linkage
if (nsnull != GetPrevInFlow()) {
fprintf(out, " prev-in-flow=%p", static_cast<void*>(GetPrevInFlow()));
}
if (nsnull != GetNextInFlow()) {
fprintf(out, " next-in-flow=%p", static_cast<void*>(GetNextInFlow()));
}
// 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);
}
nsBlockFrame* f = const_cast<nsBlockFrame*>(this);
nsRect* overflowArea = f->GetOverflowAreaProperty(PR_FALSE);
if (overflowArea) {
fprintf(out, " [overflow=%d,%d,%d,%d]", overflowArea->x, overflowArea->y,
overflowArea->width, overflowArea->height);
}
PRInt32 numInlineLines = 0;
PRInt32 numBlockLines = 0;
if (! mLines.empty()) {
for (const_line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line)
{
if (line->IsBlock())
numBlockLines++;
else
numInlineLines++;
}
}
fprintf(out, " sc=%p(i=%d,b=%d)",
static_cast<void*>(mStyleContext), numInlineLines, numBlockLines);
nsIAtom* pseudoTag = mStyleContext->GetPseudoType();
if (pseudoTag) {
nsAutoString atomString;
pseudoTag->ToString(atomString);
fprintf(out, " pst=%s",
NS_LossyConvertUTF16toASCII(atomString).get());
}
fputs("<\n", out);
aIndent++;
// Output the lines
if (! mLines.empty()) {
for (const_line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line)
{
line->List(out, aIndent);
}
}
nsIAtom* listName = nsnull;
PRInt32 listIndex = 0;
for (;;) {
listName = GetAdditionalChildListName(listIndex++);
if (nsnull == listName) {
break;
}
nsIFrame* kid = GetFirstChild(listName);
if (kid) {
IndentBy(out, aIndent);
nsAutoString tmp;
if (nsnull != listName) {
listName->ToString(tmp);
fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
}
fputs("<\n", out);
while (kid) {
nsIFrameDebug* frameDebug;
if (NS_SUCCEEDED(CallQueryInterface(kid, &frameDebug))) {
frameDebug->List(out, aIndent + 1);
}
kid = kid->GetNextSibling();
}
IndentBy(out, aIndent);
fputs(">\n", out);
}
}
aIndent--;
IndentBy(out, aIndent);
fputs(">\n", out);
return NS_OK;
}
NS_IMETHODIMP_(nsFrameState)
nsBlockFrame::GetDebugStateBits() const
{
// We don't want to include our cursor flag in the bits the
// regression tester looks at
return nsBlockFrameSuper::GetDebugStateBits() & ~NS_BLOCK_HAS_LINE_CURSOR;
}
NS_IMETHODIMP
nsBlockFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("Block"), aResult);
}
#endif
nsIAtom*
nsBlockFrame::GetType() const
{
return nsGkAtoms::blockFrame;
}
void
nsBlockFrame::InvalidateInternal(const nsRect& aDamageRect,
nscoord aX, nscoord aY, nsIFrame* aForChild,
PRBool aImmediate)
{
// Optimize by suppressing invalidation of areas that are clipped out
// with CSS 'clip'.
const nsStyleDisplay* disp = GetStyleDisplay();
nsRect absPosClipRect;
if (GetAbsPosClipRect(disp, &absPosClipRect, GetSize())) {
// Restrict the invalidated area to abs-pos clip rect
// abs-pos clipping clips everything in the frame
nsRect r;
if (r.IntersectRect(aDamageRect, absPosClipRect - nsPoint(aX, aY))) {
nsBlockFrameSuper::InvalidateInternal(r, aX, aY, this, aImmediate);
}
return;
}
nsBlockFrameSuper::InvalidateInternal(aDamageRect, aX, aY, this, aImmediate);
}
nscoord
nsBlockFrame::GetBaseline() const
{
NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "frame must not be dirty");
nscoord result;
if (nsLayoutUtils::GetLastLineBaseline(this, &result))
return result;
return nsFrame::GetBaseline();
}
/////////////////////////////////////////////////////////////////////////////
// Child frame enumeration
nsIFrame*
nsBlockFrame::GetFirstChild(nsIAtom* aListName) const
{
if (nsGkAtoms::absoluteList == aListName) {
nsIFrame* result = nsnull;
mAbsoluteContainer.FirstChild(this, aListName, &result);
return result;
}
else if (nsnull == aListName) {
return (mLines.empty()) ? nsnull : mLines.front()->mFirstChild;
}
else if (aListName == nsGkAtoms::overflowList) {
nsLineList* overflowLines = GetOverflowLines();
return overflowLines ? overflowLines->front()->mFirstChild : nsnull;
}
else if (aListName == nsGkAtoms::overflowOutOfFlowList) {
return GetOverflowOutOfFlows().FirstChild();
}
else if (aListName == nsGkAtoms::floatList) {
return mFloats.FirstChild();
}
else if (aListName == nsGkAtoms::bulletList) {
return (HaveOutsideBullet()) ? mBullet : nsnull;
}
return nsContainerFrame::GetFirstChild(aListName);;
}
#define NS_BLOCK_FRAME_OVERFLOW_OOF_LIST_INDEX (NS_CONTAINER_LIST_COUNT_INCL_OC + 0)
#define NS_BLOCK_FRAME_FLOAT_LIST_INDEX (NS_CONTAINER_LIST_COUNT_INCL_OC + 1)
#define NS_BLOCK_FRAME_BULLET_LIST_INDEX (NS_CONTAINER_LIST_COUNT_INCL_OC + 2)
#define NS_BLOCK_FRAME_ABSOLUTE_LIST_INDEX (NS_CONTAINER_LIST_COUNT_INCL_OC + 3)
// If adding/removing lists, don't forget to update the count in nsBlockFrame.h
nsIAtom*
nsBlockFrame::GetAdditionalChildListName(PRInt32 aIndex) const
{
if (aIndex < NS_CONTAINER_LIST_COUNT_INCL_OC)
return nsContainerFrame::GetAdditionalChildListName(aIndex);
switch (aIndex) {
case NS_BLOCK_FRAME_FLOAT_LIST_INDEX:
return nsGkAtoms::floatList;
case NS_BLOCK_FRAME_BULLET_LIST_INDEX:
return nsGkAtoms::bulletList;
case NS_BLOCK_FRAME_OVERFLOW_OOF_LIST_INDEX:
return nsGkAtoms::overflowOutOfFlowList;
case NS_BLOCK_FRAME_ABSOLUTE_LIST_INDEX:
return nsGkAtoms::absoluteList;
default:
return nsnull;
}
}
/* virtual */ PRBool
nsBlockFrame::IsContainingBlock() const
{
return PR_TRUE;
}
/* virtual */ PRBool
nsBlockFrame::IsFloatContainingBlock() const
{
return PR_TRUE;
}
static PRBool IsContinuationPlaceholder(nsIFrame* aFrame)
{
return aFrame->GetPrevInFlow() &&
nsGkAtoms::placeholderFrame == aFrame->GetType();
}
static void ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent,
nsIFrame* aNewParent) {
NS_ASSERTION(aOldParent == aFrame->GetParent(),
"Parent not consistent with exepectations");
aFrame->SetParent(aNewParent);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsHTMLContainerFrame::ReparentFrameView(aFrame->PresContext(), aFrame,
aOldParent, aNewParent);
}
//////////////////////////////////////////////////////////////////////
// Frame structure methods
//////////////////////////////////////////////////////////////////////
// Reflow methods
/* virtual */ void
nsBlockFrame::MarkIntrinsicWidthsDirty()
{
nsBlockFrame* dirtyBlock = NS_STATIC_CAST(nsBlockFrame*, GetFirstContinuation());
dirtyBlock->mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
dirtyBlock->mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
dirtyBlock->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
nsBlockFrameSuper::MarkIntrinsicWidthsDirty();
}
/* virtual */ nscoord
nsBlockFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
{
nsIFrame* firstInFlow = GetFirstContinuation();
if (firstInFlow != this)
return firstInFlow->GetMinWidth(aRenderingContext);
DISPLAY_MIN_WIDTH(this, mMinWidth);
if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
return mMinWidth;
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": GetMinWidth\n");
}
AutoNoisyIndenter indent(gNoisyIntrinsic);
#endif
ResolveBidi();
InlineMinWidthData data;
for (nsBlockFrame* curFrame = this; curFrame;
curFrame = NS_STATIC_CAST(nsBlockFrame*, curFrame->GetNextContinuation())) {
for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines();
line != line_end; ++line)
{
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("line (%s%s)\n",
line->IsBlock() ? "block" : "inline",
line->IsEmpty() ? ", empty" : "");
}
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
#endif
if (line->IsBlock()) {
data.ForceBreak(aRenderingContext);
data.currentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
line->mFirstChild, nsLayoutUtils::MIN_WIDTH);
data.ForceBreak(aRenderingContext);
} else {
if (line == begin_lines()) {
const nsStyleCoord &indent = GetStyleText()->mTextIndent;
if (indent.GetUnit() == eStyleUnit_Coord)
data.currentLine += indent.GetCoordValue();
}
// XXX Bug NNNNNN Should probably handle percentage text-indent.
nsIFrame *kid = line->mFirstChild;
for (PRInt32 i = 0, i_end = line->GetChildCount(); i != i_end;
++i, kid = kid->GetNextSibling()) {
kid->AddInlineMinWidth(aRenderingContext, &data);
}
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("min: [prevLines=%d currentLine=%d]\n",
data.prevLines, data.currentLine);
}
#endif
}
}
data.ForceBreak(aRenderingContext);
mMinWidth = data.prevLines;
return mMinWidth;
}
/* virtual */ nscoord
nsBlockFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
{
nsIFrame* firstInFlow = GetFirstContinuation();
if (firstInFlow != this)
return firstInFlow->GetPrefWidth(aRenderingContext);
DISPLAY_PREF_WIDTH(this, mPrefWidth);
if (mPrefWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
return mPrefWidth;
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": GetPrefWidth\n");
}
AutoNoisyIndenter indent(gNoisyIntrinsic);
#endif
ResolveBidi();
InlinePrefWidthData data;
for (nsBlockFrame* curFrame = this; curFrame;
curFrame = NS_STATIC_CAST(nsBlockFrame*, curFrame->GetNextContinuation())) {
for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines();
line != line_end; ++line)
{
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("line (%s%s)\n",
line->IsBlock() ? "block" : "inline",
line->IsEmpty() ? ", empty" : "");
}
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
#endif
if (line->IsBlock()) {
data.ForceBreak(aRenderingContext);
data.currentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
line->mFirstChild, nsLayoutUtils::PREF_WIDTH);
data.ForceBreak(aRenderingContext);
} else {
if (line == begin_lines()) {
const nsStyleCoord &indent = GetStyleText()->mTextIndent;
if (indent.GetUnit() == eStyleUnit_Coord)
data.currentLine += indent.GetCoordValue();
}
// XXX Bug NNNNNN Should probably handle percentage text-indent.
nsIFrame *kid = line->mFirstChild;
for (PRInt32 i = 0, i_end = line->GetChildCount(); i != i_end;
++i, kid = kid->GetNextSibling()) {
kid->AddInlinePrefWidth(aRenderingContext, &data);
}
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("pref: [prevLines=%d currentLine=%d]\n",
data.prevLines, data.currentLine);
}
#endif
}
}
data.ForceBreak(aRenderingContext);
mPrefWidth = data.prevLines;
return mPrefWidth;
}
static nsSize
CalculateContainingBlockSizeForAbsolutes(const nsHTMLReflowState& aReflowState,
nsSize aFrameSize)
{
// The issue here is that for a 'height' of 'auto' the reflow state
// code won't know how to calculate the containing block height
// because it's calculated bottom up. So we use our own computed
// size as the dimensions. We don't really want to do this for the
// initial containing block
nsIFrame* frame = aReflowState.frame;
if (nsLayoutUtils::IsInitialContainingBlock(frame)) {
return nsSize(-1, -1);
}
nsSize cbSize(aFrameSize);
// Containing block is relative to the padding edge
const nsMargin& border = aReflowState.mStyleBorder->GetBorder();
cbSize.width -= border.left + border.right;
cbSize.height -= border.top + border.bottom;
if (frame->GetParent()->GetContent() == frame->GetContent()) {
// We are a wrapped frame for the content. Use the container's
// dimensions, if they have been precomputed.
// XXX This is a hack! We really should be waiting until the outermost
// frame is fully reflowed and using the resulting dimensions, even
// if they're intrinsic.
// In fact we should be attaching absolute children to the outermost
// frame and not always sticking them in block frames.
// First, find the reflow state for the outermost frame for this
// content.
const nsHTMLReflowState* aLastRS = &aReflowState;
const nsHTMLReflowState* lastButOneRS = &aReflowState;
while (aLastRS->parentReflowState &&
aLastRS->parentReflowState->frame->GetContent() == frame->GetContent()) {
lastButOneRS = aLastRS;
aLastRS = aLastRS->parentReflowState;
}
if (aLastRS != &aReflowState) {
// Scrollbars need to be specifically excluded, if present, because they are outside the
// padding-edge. We need better APIs for getting the various boxes from a frame.
nsIScrollableFrame* scrollFrame;
CallQueryInterface(aLastRS->frame, &scrollFrame);
nsMargin scrollbars(0,0,0,0);
if (scrollFrame) {
nsBoxLayoutState dummyState(aLastRS->frame->PresContext(),
aLastRS->rendContext);
scrollbars = scrollFrame->GetDesiredScrollbarSizes(&dummyState);
// XXX We should account for the horizontal scrollbar too --- but currently
// nsGfxScrollFrame assumes nothing depends on the presence (or absence) of
// a horizontal scrollbar, so accounting for it would create incremental
// reflow bugs.
//if (!lastButOneRS->mFlags.mAssumingHScrollbar) {
scrollbars.top = scrollbars.bottom = 0;
//}
if (!lastButOneRS->mFlags.mAssumingVScrollbar) {
scrollbars.left = scrollbars.right = 0;
}
}
// We found a reflow state for the outermost wrapping frame, so use
// its computed metrics if available
if (aLastRS->ComputedWidth() != NS_UNCONSTRAINEDSIZE) {
cbSize.width = PR_MAX(0,
aLastRS->ComputedWidth() + aLastRS->mComputedPadding.LeftRight() - scrollbars.LeftRight());
}
if (aLastRS->ComputedHeight() != NS_UNCONSTRAINEDSIZE) {
cbSize.height = PR_MAX(0,
aLastRS->ComputedHeight() + aLastRS->mComputedPadding.TopBottom() - scrollbars.TopBottom());
}
}
}
return cbSize;
}
NS_IMETHODIMP
nsBlockFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, 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.ComputedWidth(), aReflowState.ComputedHeight());
}
AutoNoisyIndenter indent(gNoisy);
PRTime start = LL_ZERO; // Initialize these variablies to silence the compiler.
PRInt32 ctc = 0; // We only use these if they are set (gLameReflowMetrics).
if (gLameReflowMetrics) {
start = PR_Now();
ctc = nsLineBox::GetCtorCount();
}
#endif
nsSize oldSize = GetSize();
// Should we create a space manager?
nsAutoSpaceManager autoSpaceManager(const_cast<nsHTMLReflowState &>(aReflowState));
// XXXldb If we start storing the space manager in the frame rather
// than keeping it around only during reflow then we should create it
// only when there are actually floats to manage. Otherwise things
// like tables will gain significant bloat.
PRBool needSpaceManager = nsBlockFrame::BlockNeedsSpaceManager(this);
if (needSpaceManager)
autoSpaceManager.CreateSpaceManagerFor(aPresContext, this);
// OK, some lines may be reflowed. Blow away any saved line cursor because
// we may invalidate the nondecreasing combinedArea.y/yMost invariant,
// and we may even delete the line with the line cursor.
ClearLineCursor();
if (IsFrameTreeTooDeep(aReflowState, aMetrics)) {
#ifdef DEBUG_kipp
{
extern char* nsPresShell_ReflowStackPointerTop;
char marker;
char* newsp = (char*) &marker;
printf("XXX: frame tree is too deep; approx stack size = %d\n",
nsPresShell_ReflowStackPointerTop - newsp);
}
#endif
aStatus = NS_FRAME_COMPLETE;
return NS_OK;
}
PRBool marginRoot = BlockIsMarginRoot(this);
nsBlockReflowState state(aReflowState, aPresContext, this, aMetrics,
marginRoot, marginRoot, needSpaceManager);
#ifdef IBMBIDI
ResolveBidi();
#endif // IBMBIDI
if (RenumberLists(aPresContext)) {
AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
nsresult rv = NS_OK;
// ALWAYS drain overflow. We never want to leave the previnflow's
// overflow lines hanging around; block reflow depends on the
// overflow line lists being cleared out between reflow passes.
DrainOverflowLines(state);
state.SetupOverflowPlaceholdersProperty();
// If we're not dirty (which means we'll mark everything dirty later)
// and our width has changed, mark the lines dirty that we need to
// mark dirty for a resize reflow.
if (aReflowState.mFlags.mHResize)
PrepareResizeReflow(state);
mState &= ~NS_FRAME_FIRST_REFLOW;
// Now reflow...
rv = ReflowDirtyLines(state);
NS_ASSERTION(NS_SUCCEEDED(rv), "reflow dirty lines failed");
if (NS_FAILED(rv)) return rv;
// Handle paginated overflow (see nsContainerFrame.h)
nsRect overflowContainerBounds;
if (GetPrevInFlow()) {
ReflowOverflowContainerChildren(aPresContext, aReflowState,
overflowContainerBounds, 0,
state.mReflowStatus);
}
// If the block is complete, put continuted floats in the closest ancestor
// block that uses the same space manager and leave the block complete; this
// allows subsequent lines on the page to be impacted by floats. If the
// block is incomplete or there is no ancestor using the same space manager,
// put continued floats at the beginning of the first overflow line.
if (state.mOverflowPlaceholders.NotEmpty()) {
NS_ASSERTION(aReflowState.availableHeight != NS_UNCONSTRAINEDSIZE,
"Somehow we failed to fit all content, even though we have unlimited space!");
if (NS_FRAME_IS_FULLY_COMPLETE(state.mReflowStatus)) {
// find the nearest block ancestor that uses the same space manager
for (const nsHTMLReflowState* ancestorRS = aReflowState.parentReflowState;
ancestorRS;
ancestorRS = ancestorRS->parentReflowState) {
nsIFrame* ancestor = ancestorRS->frame;
nsIAtom* fType = ancestor->GetType();
if ((nsGkAtoms::blockFrame == fType || nsGkAtoms::areaFrame == fType) &&
aReflowState.mSpaceManager == ancestorRS->mSpaceManager) {
// Put the continued floats in ancestor since it uses the same space manager
nsFrameList* ancestorPlace =
((nsBlockFrame*)ancestor)->GetOverflowPlaceholders();
// The ancestor should have this list, since it's being reflowed. But maybe
// it isn't because of reflow roots or something.
if (ancestorPlace) {
for (nsIFrame* f = state.mOverflowPlaceholders.FirstChild();
f; f = f->GetNextSibling()) {
NS_ASSERTION(IsContinuationPlaceholder(f),
"Overflow placeholders must be continuation placeholders");
ReparentFrame(f, this, ancestorRS->frame);
nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
mFloats.RemoveFrame(oof);
ReparentFrame(oof, this, ancestorRS->frame);
// Clear the next-sibling in case the frame wasn't in mFloats
oof->SetNextSibling(nsnull);
// Do not put the float into any child frame list, because
// placeholders in the overflow-placeholder block-state list
// don't keep their out of flows in a child frame list.
}
ancestorPlace->AppendFrames(nsnull, state.mOverflowPlaceholders.FirstChild());
state.mOverflowPlaceholders.SetFrames(nsnull);
break;
}
}
}
}
if (!state.mOverflowPlaceholders.IsEmpty()) {
state.mOverflowPlaceholders.SortByContentOrder();
PRInt32 numOverflowPlace = state.mOverflowPlaceholders.GetLength();
nsLineBox* newLine =
state.NewLineBox(state.mOverflowPlaceholders.FirstChild(),
numOverflowPlace, PR_FALSE);
if (newLine) {
nsLineList* overflowLines = GetOverflowLines();
if (overflowLines) {
// Need to put the overflow placeholders' floats into our
// overflow-out-of-flows list, since the overflow placeholders are
// going onto our overflow line list. Put them last, because that's
// where the placeholders are going.
nsFrameList floats;
nsIFrame* lastFloat = nsnull;
for (nsIFrame* f = state.mOverflowPlaceholders.FirstChild();
f; f = f->GetNextSibling()) {
NS_ASSERTION(IsContinuationPlaceholder(f),
"Overflow placeholders must be continuation placeholders");
nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
// oof is not currently in any child list
floats.InsertFrames(nsnull, lastFloat, oof);
lastFloat = oof;
}
// Put the new placeholders *last* in the overflow lines
// because they might have previnflows in the overflow lines.
nsIFrame* lastChild = overflowLines->back()->LastChild();
lastChild->SetNextSibling(state.mOverflowPlaceholders.FirstChild());
// Create a new line as the last line and put the
// placeholders there
overflowLines->push_back(newLine);
nsAutoOOFFrameList oofs(this);
oofs.mList.AppendFrames(nsnull, floats.FirstChild());
}
else {
mLines.push_back(newLine);
nsLineList::iterator nextToLastLine = ----end_lines();
PushLines(state, nextToLastLine);
}
state.mOverflowPlaceholders.SetFrames(nsnull);
}
state.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
NS_FRAME_SET_INCOMPLETE(state.mReflowStatus);
}
}
if (NS_FRAME_IS_NOT_COMPLETE(state.mReflowStatus)) {
if (GetOverflowLines()) {
state.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
}
if (NS_STYLE_OVERFLOW_CLIP == aReflowState.mStyleDisplay->mOverflowX) {
state.mReflowStatus = NS_FRAME_COMPLETE;
}
else {
#ifdef DEBUG_kipp
ListTag(stdout); printf(": block is not complete\n");
#endif
}
}
CheckFloats(state);
// Place the "marker" (bullet) frame if it is placed next to a block
// child.
//
// 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. It's 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 (mBullet && HaveOutsideBullet() &&
(mLines.empty() ||
mLines.front()->IsBlock() ||
0 == mLines.front()->mBounds.height)) {
// Reflow the bullet
nsHTMLReflowMetrics metrics;
ReflowBullet(state, metrics);
nscoord baseline;
if (!nsLayoutUtils::GetFirstLineBaseline(this, &baseline)) {
baseline = 0;
}
// Doing the alignment using the baseline will also cater for
// bullets that are placed next to a child block (bug 92896)
// Tall bullets won't look particularly nice here...
nsRect bbox = mBullet->GetRect();
bbox.y = baseline - metrics.ascent;
mBullet->SetRect(bbox);
}
// Compute our final size
ComputeFinalSize(aReflowState, state, aMetrics);
ComputeCombinedArea(aReflowState, aMetrics);
// Factor overflow container child bounds into the overflow area
aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea,
overflowContainerBounds);
// see if verifyReflow is enabled, and if so store off the space manager pointer
#ifdef DEBUG
PRInt32 verifyReflowFlags = nsIPresShell::GetVerifyReflowFlags();
if (VERIFY_REFLOW_INCLUDE_SPACE_MANAGER & verifyReflowFlags)
{
// this is a leak of the space manager, but it's only in debug if verify reflow is enabled, so not a big deal
nsIPresShell *shell = aPresContext->GetPresShell();
if (shell) {
nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState;
rv = SetProperty(nsGkAtoms::spaceManagerProperty,
reflowState.mSpaceManager,
nsnull /* should be nsSpaceManagerDestroyer*/);
autoSpaceManager.DebugOrphanSpaceManager();
}
}
#endif
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed, e.g., elements with a percentage
// based width/height
// We want to do this under either of two conditions:
// 1. If we didn't do the incremental reflow above.
// 2. If our size changed.
// Even though it's the padding edge that's the containing block, we
// can use our rect (the border edge) since if the border style
// changed, the reflow would have been targeted at us so we'd satisfy
// condition 1.
if (mAbsoluteContainer.HasAbsoluteFrames()) {
nsRect childBounds;
nsSize containingBlockSize
= CalculateContainingBlockSizeForAbsolutes(aReflowState,
nsSize(aMetrics.width, aMetrics.height));
// Mark frames that depend on changes we just made to this frame as dirty:
// Now we can assume that the padding edge hasn't moved.
// We need to reflow the absolutes if one of them depends on
// its placeholder position, or the containing block size in a
// direction in which the containing block size might have
// changed.
PRBool cbWidthChanged = aMetrics.width != oldSize.width;
PRBool isRoot = !GetContent()->GetParent();
// If isRoot and we have auto height, then we are the initial
// containing block and the containing block height is the
// viewport height, which can't change during incremental
// reflow.
PRBool cbHeightChanged =
!(isRoot && NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) &&
aMetrics.height != oldSize.height;
rv = mAbsoluteContainer.Reflow(this, aPresContext, aReflowState,
containingBlockSize.width,
containingBlockSize.height,
cbWidthChanged, cbHeightChanged,
&childBounds);
//XXXfr Why isn't this rv (and others in this file) checked/returned?
// Factor the absolutely positioned child bounds into the overflow area
aMetrics.mOverflowArea.UnionRect(aMetrics.mOverflowArea, childBounds);
}
// Determine if we need to repaint our border, background or outline
CheckInvalidateSizeChange(aPresContext, aMetrics, aReflowState);
FinishAndStoreOverflow(&aMetrics);
// Clear the space manager pointer in the block reflow state so we
// don't waste time translating the coordinate system back on a dead
// space manager.
if (needSpaceManager)
state.mSpaceManager = nsnull;
aStatus = state.mReflowStatus;
#ifdef DEBUG
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.get());
if (mState & NS_FRAME_OUTSIDE_CHILDREN) {
printf(" combinedArea={%d,%d,%d,%d}",
aMetrics.mOverflowArea.x,
aMetrics.mOverflowArea.y,
aMetrics.mOverflowArea.width,
aMetrics.mOverflowArea.height);
}
printf("\n");
}
if (gLameReflowMetrics) {
PRTime end = PR_Now();
PRInt32 ectc = nsLineBox::GetCtorCount();
PRInt32 numLines = mLines.size();
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
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
return rv;
}
PRBool
nsBlockFrame::CheckForCollapsedBottomMarginFromClearanceLine()
{
line_iterator begin = begin_lines();
line_iterator line = end_lines();
while (PR_TRUE) {
if (begin == line) {
return PR_FALSE;
}
--line;
if (line->mBounds.height != 0 || !line->CachedIsEmpty()) {
return PR_FALSE;
}
if (line->HasClearance()) {
return PR_TRUE;
}
}
// not reached
}
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.GetFlag(BRS_ISBOTTOMMARGINROOT) ? "yes" : "no",
aState.mPrevBottomMargin,
borderPadding.top, borderPadding.bottom);
#endif
// Compute final width
aMetrics.width = borderPadding.left + aReflowState.ComputedWidth() +
borderPadding.right;
// Return bottom margin information
// rbs says he hit this assertion occasionally (see bug 86947), so
// just set the margin to zero and we'll figure out why later
//NS_ASSERTION(aMetrics.mCarriedOutBottomMargin.IsZero(),
// "someone else set the margin");
nscoord nonCarriedOutVerticalMargin = 0;
if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) {
// Apply rule from CSS 2.1 section 8.3.1. If we have some empty
// line with clearance and a non-zero top margin and all
// subsequent lines are empty, then we do not allow our children's
// carried out bottom margin to be carried out of us and collapse
// with our own bottom margin.
if (CheckForCollapsedBottomMarginFromClearanceLine()) {
// Convert the children's carried out margin to something that
// we will include in our height
nonCarriedOutVerticalMargin = aState.mPrevBottomMargin.get();
aState.mPrevBottomMargin.Zero();
}
aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin;
} else {
aMetrics.mCarriedOutBottomMargin.Zero();
}
// Compute final height
if (NS_UNCONSTRAINEDSIZE != aReflowState.ComputedHeight()) {
// Figure out how much of the computed height should be
// applied to this frame.
nscoord computedHeightLeftOver = aReflowState.ComputedHeight();
if (GetPrevInFlow()) {
// Reduce the height by the computed height of prev-in-flows.
for (nsIFrame* prev = GetPrevInFlow(); prev; prev = prev->GetPrevInFlow()) {
computedHeightLeftOver -= prev->GetRect().height;
}
// We just subtracted our top-border padding, since it was included in the
// first frame's height. Add it back to get the content height.
computedHeightLeftOver += aReflowState.mComputedBorderPadding.top;
// We may have stretched the frame beyond its computed height. Oh well.
computedHeightLeftOver = PR_MAX(0, computedHeightLeftOver);
}
NS_ASSERTION(!( (mState & NS_FRAME_IS_OVERFLOW_CONTAINER)
&& computedHeightLeftOver ),
"overflow container must not have computedHeightLeftOver");
aMetrics.height = borderPadding.top + computedHeightLeftOver + borderPadding.bottom;
if (NS_FRAME_IS_NOT_COMPLETE(aState.mReflowStatus)
&& aMetrics.height < aReflowState.availableHeight) {
// We ran out of height on this page but we're incomplete
// Set status to complete except for overflow
NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus);
}
if (NS_FRAME_IS_COMPLETE(aState.mReflowStatus)) {
if (computedHeightLeftOver > 0 &&
NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight &&
aMetrics.height > aReflowState.availableHeight) {
// We don't fit and we consumed some of the computed height,
// so we should consume all the available height and then
// break. If our bottom border/padding straddles the break
// point, then this will increase our height and push the
// border/padding to the next page/column.
aMetrics.height = PR_MAX(aReflowState.availableHeight,
aState.mY + nonCarriedOutVerticalMargin);
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
if (!GetNextInFlow())
aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
}
}
else {
// Use the current height; continuations will take up the rest.
// Do extend the height to at least consume the available
// height, otherwise our left/right borders (for example) won't
// extend all the way to the break.
aMetrics.height = PR_MAX(aReflowState.availableHeight,
aState.mY + nonCarriedOutVerticalMargin);
// ... but don't take up more height than is available
aMetrics.height = PR_MIN(aMetrics.height,
borderPadding.top + computedHeightLeftOver);
// XXX It's pretty wrong that our bottom border still gets drawn on
// on its own on the last-in-flow, even if we ran out of height
// here. We need GetSkipSides to check whether we ran out of content
// height in the current frame, not whether it's last-in-flow.
}
// Don't carry out a bottom margin when our height is fixed.
aMetrics.mCarriedOutBottomMargin.Zero();
}
else {
nscoord autoHeight = aState.mY + nonCarriedOutVerticalMargin;
// Shrink wrap our height around our contents.
if (aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) {
// When we are a bottom-margin root make sure that our last
// childs bottom margin is fully applied.
// Apply the margin only if there's space for it.
if (autoHeight < aState.mReflowState.availableHeight)
{
// Truncate bottom margin if it doesn't fit to our available height.
autoHeight = PR_MIN(autoHeight + aState.mPrevBottomMargin.get(), aState.mReflowState.availableHeight);
}
}
if (aState.GetFlag(BRS_SPACE_MGR)) {
// Include the space manager's state to properly account for the
// bottom margin of any floated elements; e.g., inside a table cell.
nscoord ymost;
if (aReflowState.mSpaceManager->YMost(ymost) &&
autoHeight < ymost)
autoHeight = ymost;
}
// Apply min/max values
autoHeight -= borderPadding.top;
nscoord oldAutoHeight = autoHeight;
aReflowState.ApplyMinMaxConstraints(nsnull, &autoHeight);
if (autoHeight != oldAutoHeight) {
// Our min-height or max-height made our height change. Don't carry out
// our kids' bottom margins.
aMetrics.mCarriedOutBottomMargin.Zero();
}
autoHeight += borderPadding.top + borderPadding.bottom;
aMetrics.height = autoHeight;
}
#ifdef DEBUG_blocks
if (CRAZY_WIDTH(aMetrics.width) || CRAZY_HEIGHT(aMetrics.height)) {
ListTag(stdout);
printf(": WARNING: desired:%d,%d\n", aMetrics.width, aMetrics.height);
}
#endif
}
void
nsBlockFrame::ComputeCombinedArea(const nsHTMLReflowState& aReflowState,
nsHTMLReflowMetrics& aMetrics)
{
// Compute the combined area of our children
// XXX_perf: This can be done incrementally. It is currently one of
// the things that makes incremental reflow O(N^2).
nsRect area(0, 0, aMetrics.width, aMetrics.height);
if (NS_STYLE_OVERFLOW_CLIP != aReflowState.mStyleDisplay->mOverflowX) {
for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line) {
area.UnionRect(area, line->GetCombinedArea());
}
// 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).
// XXXldb Can we just fix GetCombinedArea instead?
if (mBullet) {
area.UnionRect(area, mBullet->GetRect());
}
}
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
printf(": ca=%d,%d,%d,%d\n", area.x, area.y, area.width, area.height);
#endif
aMetrics.mOverflowArea = area;
}
nsresult
nsBlockFrame::MarkLineDirty(line_iterator aLine)
{
// Mark aLine dirty
aLine->MarkDirty();
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
}
#endif
// Mark previous line dirty if it's 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 (aLine != mLines.front() &&
aLine->IsInline() &&
aLine.prev()->IsInline()) {
aLine.prev()->MarkDirty();
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": mark prev-line %p dirty\n",
static_cast<void*>(aLine.prev().get()));
}
#endif
}
return NS_OK;
}
nsresult
nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState)
{
// we need to calculate if any part of then block itself
// is impacted by a float (bug 19579)
aState.GetAvailableSpace();
const nsStyleText* styleText = GetStyleText();
// See if we can try and avoid marking all the lines as dirty
PRBool tryAndSkipLines =
// There must be no floats.
!aState.IsImpactedByFloat() &&
// The text must be left-aligned.
(NS_STYLE_TEXT_ALIGN_LEFT == styleText->mTextAlign ||
(NS_STYLE_TEXT_ALIGN_DEFAULT == styleText->mTextAlign &&
NS_STYLE_DIRECTION_LTR ==
aState.mReflowState.mStyleVisibility->mDirection)) &&
// The left content-edge must be a constant distance from the left
// border-edge.
GetStylePadding()->mPadding.GetLeftUnit() != eStyleUnit_Percent;
#ifdef DEBUG
if (gDisableResizeOpt) {
tryAndSkipLines = PR_FALSE;
}
if (gNoisyReflow) {
if (!tryAndSkipLines) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": marking all lines dirty: availWidth=%d textAlign=%d\n",
aState.mReflowState.availableWidth,
styleText->mTextAlign);
}
}
#endif
if (tryAndSkipLines) {
nscoord newAvailWidth = aState.mReflowState.mComputedBorderPadding.left +
aState.mReflowState.ComputedWidth();
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mReflowState.mComputedBorderPadding.left &&
NS_UNCONSTRAINEDSIZE != aState.mReflowState.ComputedWidth(),
"math on NS_UNCONSTRAINEDSIZE");
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": trying to avoid marking all lines dirty\n");
}
#endif
for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line)
{
// We let child blocks make their own decisions the same
// way we are here.
if (line->IsBlock() ||
line->HasFloats() ||
(line != mLines.back() && !line->HasBreakAfter()) ||
line->ResizeReflowOptimizationDisabled() ||
line->IsImpactedByFloat() ||
(line->mBounds.XMost() > newAvailWidth)) {
line->MarkDirty();
}
#ifdef REALLY_NOISY_REFLOW
if (!line->IsBlock()) {
printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
line.get(), line->IsImpactedByFloat() ? "" : "not ");
}
#endif
#ifdef DEBUG
if (gNoisyReflow && !line->IsDirty()) {
IndentBy(stdout, gNoiseIndent + 1);
printf("skipped: line=%p next=%p %s %s%s%s breakTypeBefore/After=%d/%d xmost=%d\n",
static_cast<void*>(line.get()),
static_cast<void*>((line.next() != end_lines() ? line.next().get() : nsnull)),
line->IsBlock() ? "block" : "inline",
line->HasBreakAfter() ? "has-break-after " : "",
line->HasFloats() ? "has-floats " : "",
line->IsImpactedByFloat() ? "impacted " : "",
line->GetBreakTypeBefore(), line->GetBreakTypeAfter(),
line->mBounds.XMost());
}
#endif
}
}
else {
// Mark everything dirty
for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line)
{
line->MarkDirty();
}
}
return NS_OK;
}
//----------------------------------------
nsBlockFrame::line_iterator
nsBlockFrame::FindLineFor(nsIFrame* aFrame)
{
NS_PRECONDITION(aFrame, "why pass a null frame?");
line_iterator line = begin_lines(),
line_end = end_lines();
for ( ; line != line_end; ++line) {
// If the target frame is in-flow, and this line contains the it,
// then we've found our line.
if (line->Contains(aFrame))
return line;
// If the target frame is floated, and this line contains the
// float's placeholder, then we've found our line.
if (line->HasFloats()) {
for (nsFloatCache *fc = line->GetFirstFloat();
fc != nsnull;
fc = fc->Next()) {
if (aFrame == fc->mPlaceholder->GetOutOfFlowFrame())
return line;
}
}
}
return line_end;
}
/**
* Propagate reflow "damage" from from earlier lines to the current
* line. The reflow damage comes from the following sources:
* 1. The regions of float damage remembered during reflow.
* 2. The combination of nonzero |aDeltaY| and any impact by a float,
* either the previous reflow or now.
*
* When entering this function, |aLine| is still at its old position and
* |aDeltaY| indicates how much it will later be slid (assuming it
* doesn't get marked dirty and reflowed entirely).
*/
void
nsBlockFrame::PropagateFloatDamage(nsBlockReflowState& aState,
nsLineBox* aLine,
nscoord aDeltaY)
{
nsSpaceManager *spaceManager = aState.mReflowState.mSpaceManager;
NS_ASSERTION((aState.mReflowState.parentReflowState &&
aState.mReflowState.parentReflowState->mSpaceManager == spaceManager) ||
aState.mReflowState.mBlockDelta == 0, "Bad block delta passed in");
// Check to see if there are any floats; if there aren't, there can't
// be any float damage
if (!spaceManager->HasAnyFloats())
return;
// Check the damage region recorded in the float damage.
if (spaceManager->HasFloatDamage()) {
nscoord lineYA = aLine->mBounds.y + aDeltaY;
nscoord lineYB = lineYA + aLine->mBounds.height;
if (spaceManager->IntersectsDamage(lineYA, lineYB)) {
aLine->MarkDirty();
return;
}
}
// Check if the line is moving relative to the space manager
if (aDeltaY + aState.mReflowState.mBlockDelta != 0) {
if (aLine->IsBlock()) {
// Unconditionally reflow sliding blocks; we only really need to reflow
// if there's a float impacting this block, but the current space manager
// makes it difficult to check that. Therefore, we let the child block
// decide what it needs to reflow.
aLine->MarkDirty();
} else {
// Note that this check will become incorrect once bug 25888 is fixed
// because we are only checking the top of the line
aState.GetAvailableSpace(aLine->mBounds.y + aDeltaY, PR_FALSE);
PRBool wasImpactedByFloat = aLine->IsImpactedByFloat();
PRBool isImpactedByFloat = aState.IsImpactedByFloat();
#ifdef REALLY_NOISY_REFLOW
printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n",
this, wasImpactedByFloat, isImpactedByFloat);
#endif
// Mark the line dirty if it was or is affected by a float
// We actually only really need to reflow if the amount of impact
// changes, but that's not straightforward to check
if (wasImpactedByFloat || isImpactedByFloat) {
aLine->MarkDirty();
}
}
}
}
static void PlaceFrameView(nsIFrame* aFrame);
static PRBool LineHasClear(nsLineBox* aLine) {
return aLine->GetBreakTypeBefore() || aLine->HasFloatBreakAfter()
|| (aLine->IsBlock() && (aLine->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN));
}
/**
* Reparent a whole list of floats from aOldParent to this block. The
* floats might be taken from aOldParent's overflow list. They will be
* removed from the list. They end up appended to our mFloats list.
*/
void
nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame,
nsBlockFrame* aOldParent, PRBool aFromOverflow,
PRBool aReparentSiblings) {
nsFrameList list;
nsIFrame* tail = nsnull;
aOldParent->CollectFloats(aFirstFrame, list, &tail, aFromOverflow, aReparentSiblings);
if (list.NotEmpty()) {
for (nsIFrame* f = list.FirstChild(); f; f = f->GetNextSibling()) {
ReparentFrame(f, aOldParent, this);
}
mFloats.AppendFrames(nsnull, list.FirstChild());
}
}
static void DumpLine(const nsBlockReflowState& aState, nsLineBox* aLine,
nscoord aDeltaY, PRInt32 aDeltaIndent) {
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsRect lca(aLine->GetCombinedArea());
nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
printf("line=%p mY=%d dirty=%s oldBounds={%d,%d,%d,%d} oldCombinedArea={%d,%d,%d,%d} deltaY=%d mPrevBottomMargin=%d childCount=%d\n",
static_cast<void*>(aLine), aState.mY,
aLine->IsDirty() ? "yes" : "no",
aLine->mBounds.x, aLine->mBounds.y,
aLine->mBounds.width, aLine->mBounds.height,
lca.x, lca.y, lca.width, lca.height,
aDeltaY, aState.mPrevBottomMargin.get(), aLine->GetChildCount());
}
#endif
}
/**
* Reflow the dirty lines
*/
nsresult
nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
{
nsresult rv = NS_OK;
PRBool keepGoing = PR_TRUE;
PRBool repositionViews = PR_FALSE; // should we really need this?
PRBool foundAnyClears = PR_FALSE;
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": reflowing dirty lines");
printf(" computedWidth=%d\n", aState.mReflowState.ComputedWidth());
}
AutoNoisyIndenter indent(gNoisyReflow);
#endif
PRBool selfDirty = (GetStateBits() & NS_FRAME_IS_DIRTY) ||
(aState.mReflowState.mFlags.mVResize &&
(GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT));
// the amount by which we will slide the current line if it is not
// dirty
nscoord deltaY = 0;
// whether we did NOT reflow the previous line and thus we need to
// recompute the carried out margin before the line if we want to
// reflow it or if its previous margin is dirty
PRBool needToRecoverState = PR_FALSE;
PRBool reflowedFloat = PR_FALSE;
PRBool lastLineMovedUp = PR_FALSE;
// We save up information about BR-clearance here
PRUint8 inlineFloatBreakType = NS_STYLE_CLEAR_NONE;
line_iterator line = begin_lines(), line_end = end_lines();
// Reflow the lines that are already ours
for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) {
DumpLine(aState, line, deltaY, 0);
#ifdef DEBUG
AutoNoisyIndenter indent2(gNoisyReflow);
#endif
if (selfDirty)
line->MarkDirty();
// This really sucks, but we have to look inside any blocks that have clear
// elements inside them.
// XXX what can we do smarter here?
if (!line->IsDirty() && line->IsBlock() &&
(line->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)) {
line->MarkDirty();
}
// We have to reflow the line if it's a block whose clearance
// might have changed, so detect that.
if (!line->IsDirty() && line->GetBreakTypeBefore() != NS_STYLE_CLEAR_NONE) {
nscoord curY = aState.mY;
// See where we would be after applying any clearance due to
// BRs.
if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) {
curY = aState.ClearFloats(curY, inlineFloatBreakType);
}
nscoord newY = aState.ClearFloats(curY, line->GetBreakTypeBefore());
if (line->HasClearance()) {
// Reflow the line if it might not have clearance anymore.
if (newY == curY
// aState.mY is the clearance point which should be the
// top border-edge of the block frame. If sliding the
// block by deltaY isn't going to put it in the predicted
// position, then we'd better reflow the line.
|| newY != line->mBounds.y + deltaY) {
line->MarkDirty();
}
} else {
// Reflow the line if the line might have clearance now.
if (curY != newY) {
line->MarkDirty();
}
}
}
// We might have to reflow a line that is after a clearing BR.
if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) {
aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType);
if (aState.mY != line->mBounds.y + deltaY) {
// SlideLine is not going to put the line where the clearance
// put it. Reflow the line to be sure.
line->MarkDirty();
}
inlineFloatBreakType = NS_STYLE_CLEAR_NONE;
}
PRBool previousMarginWasDirty = line->IsPreviousMarginDirty();
if (previousMarginWasDirty) {
// If the previous margin is dirty, reflow the current line
line->MarkDirty();
line->ClearPreviousMarginDirty();
} else if (line->mBounds.YMost() + deltaY > aState.mBottomEdge) {
// Lines that aren't dirty but get slid past our height constraint must
// be reflowed.
line->MarkDirty();
}
if (!line->IsDirty()) {
// See if there's any reflow damage that requires that we mark the
// line dirty.
PropagateFloatDamage(aState, line, deltaY);
}
if (needToRecoverState && line->IsDirty()) {
// We need to reconstruct the bottom margin only if we didn't
// reflow the previous line and we do need to reflow (or repair
// the top position of) the next line.
aState.ReconstructMarginAbove(line);
}
if (needToRecoverState) {
needToRecoverState = PR_FALSE;
// Update aState.mPrevChild as if we had reflowed all of the frames in
// this line. This is expensive in some cases, since it requires
// walking |GetNextSibling|.
if (line->IsDirty())
aState.mPrevChild = line.prev()->LastChild();
}
// Now repair the line and update |aState.mY| by calling
// |ReflowLine| or |SlideLine|.
if (line->IsDirty()) {
lastLineMovedUp = PR_TRUE;
PRBool maybeReflowingForFirstTime =
line->mBounds.x == 0 && line->mBounds.y == 0 &&
line->mBounds.width == 0 && line->mBounds.height == 0;
// Compute the dirty lines "before" YMost, after factoring in
// the running deltaY value - the running value is implicit in
// aState.mY.
nscoord oldY = line->mBounds.y;
nscoord oldYMost = line->mBounds.YMost();
// Reflow the dirty line. If it's an incremental reflow, then force
// it to invalidate the dirty area if necessary
rv = ReflowLine(aState, line, &keepGoing);
NS_ENSURE_SUCCESS(rv, rv);
if (line->HasFloats()) {
reflowedFloat = PR_TRUE;
}
if (!keepGoing) {
DumpLine(aState, line, deltaY, -1);
if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
}
break;
}
// Test to see whether the margin that should be carried out
// to the next line (NL) might have changed. In ReflowBlockFrame
// we call nextLine->MarkPreviousMarginDirty if the block's
// actual carried-out bottom margin changed. So here we only
// need to worry about the following effects:
// 1) the line was just created, and it might now be blocking
// a carried-out bottom margin from previous lines that
// used to reach NL from reaching NL
// 2) the line used to be empty, and is now not empty,
// thus blocking a carried-out bottom margin from previous lines
// that used to reach NL from reaching NL
// 3) the line wasn't empty, but now is, so a carried-out
// bottom margin from previous lines that didn't used to reach NL
// now does
// 4) the line might have changed in a way that affects NL's
// ShouldApplyTopMargin decision. The three things that matter
// are the line's emptiness, its adjacency to the top of the block,
// and whether it has clearance (the latter only matters if the block
// was and is adjacent to the top and empty).
//
// If the line is empty now, we can't reliably tell if the line was empty
// before, so we just assume it was and do nextLine->MarkPreviousMarginDirty.
// This means the checks in 4) are redundant; if the line is empty now
// we don't need to check 4), but if the line is not empty now and we're sure
// it wasn't empty before, any adjacency and clearance changes are irrelevant
// to the result of nextLine->ShouldApplyTopMargin.
if (line.next() != end_lines()) {
PRBool maybeWasEmpty = oldY == oldYMost;
PRBool isEmpty = line->mBounds.height == 0 && line->CachedIsEmpty();
if (maybeReflowingForFirstTime /*1*/ ||
(isEmpty || maybeWasEmpty) /*2/3/4*/) {
line.next()->MarkPreviousMarginDirty();
// since it's marked dirty, nobody will care about |deltaY|
}
}
// If the line was just reflowed for the first time, then its
// old mBounds cannot be trusted so this deltaY computation is
// bogus. But that's OK because we just did
// MarkPreviousMarginDirty on the next line which will force it
// to be reflowed, so this computation of deltaY will not be
// used.
deltaY = line->mBounds.YMost() - oldYMost;
} else {
aState.mOverflowTracker.Skip(line->mFirstChild, aState.mReflowStatus);
// Nop except for blocks (we don't create overflow container
// continuations for any inlines atm), so only checking mFirstChild
// is enough
lastLineMovedUp = deltaY < 0;
if (deltaY != 0)
SlideLine(aState, line, deltaY);
else
repositionViews = PR_TRUE;
// XXX EVIL O(N^2) EVIL
aState.RecoverStateFrom(line, deltaY);
// Keep mY up to date in case we're propagating reflow damage
// and also because our final height may depend on it. If the
// line is inlines, then only update mY if the line is not
// empty, because that's what PlaceLine does. (Empty blocks may
// want to update mY, e.g. if they have clearance.)
if (line->IsBlock() || !line->CachedIsEmpty()) {
aState.mY = line->mBounds.YMost();
}
needToRecoverState = PR_TRUE;
}
// Record if we need to clear floats before reflowing the next
// line. Note that inlineFloatBreakType will be handled and
// cleared before the next line is processed, so there is no
// need to combine break types here.
if (line->HasFloatBreakAfter()) {
inlineFloatBreakType = line->GetBreakTypeAfter();
}
if (LineHasClear(line.get())) {
foundAnyClears = PR_TRUE;
}
DumpLine(aState, line, deltaY, -1);
}
// Handle BR-clearance from the last line of the block
if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) {
aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType);
}
if (needToRecoverState) {
// Is this expensive?
aState.ReconstructMarginAbove(line);
// Update aState.mPrevChild as if we had reflowed all of the frames in
// the last line. This is expensive in some cases, since it requires
// walking |GetNextSibling|.
aState.mPrevChild = line.prev()->LastChild();
}
// Should we really have to do this?
if (repositionViews)
::PlaceFrameView(this);
// We can skip trying to pull up the next line if there is no next
// in flow or we were told not to or we know it will be futile, i.e.,
// -- the next in flow is not changing
// -- and we cannot have added more space for its first line to be
// pulled up into,
// -- it's an incremental reflow of a descendant
// -- and we didn't reflow any floats (so the available space
// didn't change)
// XXXldb We should also check that the first line of the next-in-flow
// isn't dirty.
if (aState.mNextInFlow &&
(aState.mReflowState.mFlags.mNextInFlowUntouched &&
!lastLineMovedUp &&
!(GetStateBits() & NS_FRAME_IS_DIRTY) &&
!reflowedFloat)) {
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
}
else if (aState.mNextInFlow) {
// Pull data from a next-in-flow if there's still room for more
// content here.
while (keepGoing && (nsnull != aState.mNextInFlow)) {
// Grab first line from our next-in-flow
nsBlockFrame* nextInFlow = aState.mNextInFlow;
line_iterator nifLine = nextInFlow->begin_lines();
nsLineBox *toMove;
PRBool collectOverflowFloats;
if (nifLine != nextInFlow->end_lines()) {
if (HandleOverflowPlaceholdersOnPulledLine(aState, nifLine)) {
// go around again in case the line was deleted
continue;
}
toMove = nifLine;
nextInFlow->mLines.erase(nifLine);
collectOverflowFloats = PR_FALSE;
} else {
// Grab an overflow line if there are any
nsLineList* overflowLines = nextInFlow->GetOverflowLines();
if (overflowLines &&
HandleOverflowPlaceholdersOnPulledLine(aState, overflowLines->front())) {
// go around again in case the line was deleted
continue;
}
if (!overflowLines) {
aState.mNextInFlow =
static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
continue;
}
nifLine = overflowLines->begin();
NS_ASSERTION(nifLine != overflowLines->end(),
"Stored overflow line list should not be empty");
toMove = nifLine;
nextInFlow->RemoveOverflowLines();
nifLine = overflowLines->erase(nifLine);
if (nifLine != overflowLines->end()) {
// We need to this remove-and-put-back dance because we want
// to avoid making the overflow line list empty while it's
// stored in the property (because the property has the
// invariant that the list is never empty).
nextInFlow->SetOverflowLines(overflowLines);
}
collectOverflowFloats = PR_TRUE;
}
if (0 == toMove->GetChildCount()) {
// The line is empty. Try the next one.
NS_ASSERTION(nsnull == toMove->mFirstChild, "bad empty line");
aState.FreeLineBox(toMove);
continue;
}
// XXX move to a subroutine: run-in, overflow, pullframe and this do this
// Make the children in the line ours.
nsIFrame* frame = toMove->mFirstChild;
nsIFrame* lastFrame = nsnull;
PRInt32 n = toMove->GetChildCount();
while (--n >= 0) {
ReparentFrame(frame, nextInFlow, this);
lastFrame = frame;
frame = frame->GetNextSibling();
}
lastFrame->SetNextSibling(nsnull);
// Reparent floats whose placeholders are in the line.
ReparentFloats(toMove->mFirstChild, nextInFlow, collectOverflowFloats, PR_TRUE);
// Add line to our line list
if (aState.mPrevChild) {
aState.mPrevChild->SetNextSibling(toMove->mFirstChild);
}
line = mLines.before_insert(end_lines(), toMove);
DumpLine(aState, toMove, deltaY, 0);
#ifdef DEBUG
AutoNoisyIndenter indent2(gNoisyReflow);
#endif
// 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 (line != end_lines()) {
rv = ReflowLine(aState, line, &keepGoing);
NS_ENSURE_SUCCESS(rv, rv);
DumpLine(aState, line, deltaY, -1);
if (!keepGoing) {
if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
}
break;
}
if (LineHasClear(line.get())) {
foundAnyClears = PR_TRUE;
}
// If this is an inline frame then its time to stop
++line;
aState.AdvanceToNextLine();
}
}
if (NS_FRAME_IS_NOT_COMPLETE(aState.mReflowStatus)) {
aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
} //XXXfr shouldn't set this flag when nextinflow has no lines
}
// Handle an odd-ball case: a list-item with no lines
if (mBullet && HaveOutsideBullet() && mLines.empty()) {
nsHTMLReflowMetrics metrics;
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;
}
if (foundAnyClears) {
AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
} else {
RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
}
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent - 1);
ListTag(stdout);
printf(": done reflowing dirty lines (status=%x)\n",
aState.mReflowStatus);
}
#endif
return rv;
}
void
nsBlockFrame::DeleteLine(nsBlockReflowState& aState,
nsLineList::iterator aLine,
nsLineList::iterator aLineEnd)
{
NS_PRECONDITION(0 == aLine->GetChildCount(), "can't delete !empty line");
if (0 == aLine->GetChildCount()) {
NS_ASSERTION(aState.mCurrentLine == aLine,
"using function more generally than designed, "
"but perhaps OK now");
nsLineBox *line = aLine;
aLine = mLines.erase(aLine);
aState.FreeLineBox(line);
// Mark the previous margin of the next line dirty since we need to
// recompute its top position.
if (aLine != aLineEnd)
aLine->MarkPreviousMarginDirty();
}
}
/**
* Takes two rectangles whose origins must be the same, and computes
* the difference between their union and their intersection as two
* rectangles. (This difference is a superset of the difference
* between the two rectangles.)
*/
static void GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2,
nsRect* aHStrip, nsRect* aVStrip) {
NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(),
"expected rects at the same position");
nsRect unionRect(aR1.x, aR1.y, PR_MAX(aR1.width, aR2.width),
PR_MAX(aR1.height, aR2.height));
nscoord VStripStart = PR_MIN(aR1.width, aR2.width);
nscoord HStripStart = PR_MIN(aR1.height, aR2.height);
*aVStrip = unionRect;
aVStrip->x += VStripStart;
aVStrip->width -= VStripStart;
*aHStrip = unionRect;
aHStrip->y += HStripStart;
aHStrip->height -= HStripStart;
}
/**
* Reflow a line. The line will either contain a single block frame
* or contain 1 or more inline frames. aKeepReflowGoing indicates
* whether or not the caller should continue to reflow more lines.
*/
nsresult
nsBlockFrame::ReflowLine(nsBlockReflowState& aState,
line_iterator aLine,
PRBool* aKeepReflowGoing)
{
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();
aLine->InvalidateCachedIsEmpty();
// Now that we know what kind of line we have, reflow it
if (aLine->IsBlock()) {
nsRect oldBounds = aLine->mFirstChild->GetRect();
nsRect oldCombinedArea(aLine->GetCombinedArea());
rv = ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
nsRect newBounds = aLine->mFirstChild->GetRect();
// 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.
// XXX roc --- the above statement is ambiguous about whether 'bounds'
// means the frame's bounds or overflowArea, and in fact this is a source
// of much confusion and bugs. Thus the following hack considers *both*
// overflowArea and bounds. This should be considered a temporary hack
// until we decide how it's really supposed to work.
nsRect lineCombinedArea(aLine->GetCombinedArea());
if (oldCombinedArea.TopLeft() != lineCombinedArea.TopLeft() ||
oldBounds.TopLeft() != newBounds.TopLeft()) {
// The block has moved, and so to be safe we need to repaint
// XXX We need to improve on this...
nsRect dirtyRect;
dirtyRect.UnionRect(oldCombinedArea, lineCombinedArea);
#ifdef NOISY_BLOCK_INVALIDATE
printf("%p invalidate 6 (%d, %d, %d, %d)\n",
this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
#endif
Invalidate(dirtyRect);
} else {
nsRect combinedAreaHStrip, combinedAreaVStrip;
nsRect boundsHStrip, boundsVStrip;
GetRectDifferenceStrips(oldBounds, newBounds,
&boundsHStrip, &boundsVStrip);
GetRectDifferenceStrips(oldCombinedArea, lineCombinedArea,
&combinedAreaHStrip, &combinedAreaVStrip);
#ifdef NOISY_BLOCK_INVALIDATE
printf("%p invalidate boundsVStrip (%d, %d, %d, %d)\n",
this, boundsVStrip.x, boundsVStrip.y, boundsVStrip.width, boundsVStrip.height);
printf("%p invalidate boundsHStrip (%d, %d, %d, %d)\n",
this, boundsHStrip.x, boundsHStrip.y, boundsHStrip.width, boundsHStrip.height);
printf("%p invalidate combinedAreaVStrip (%d, %d, %d, %d)\n",
this, combinedAreaVStrip.x, combinedAreaVStrip.y, combinedAreaVStrip.width, combinedAreaVStrip.height);
printf("%p invalidate combinedAreaHStrip (%d, %d, %d, %d)\n",
this, combinedAreaHStrip.x, combinedAreaHStrip.y, combinedAreaHStrip.width, combinedAreaHStrip.height);
#endif
// The first thing Invalidate does is check if the rect is empty, so
// don't bother doing that here.
Invalidate(boundsVStrip);
Invalidate(boundsHStrip);
Invalidate(combinedAreaVStrip);
Invalidate(combinedAreaHStrip);
}
}
else {
nsRect oldCombinedArea(aLine->GetCombinedArea());
aLine->SetLineWrapped(PR_FALSE);
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
nsRect dirtyRect;
dirtyRect.UnionRect(oldCombinedArea, aLine->GetCombinedArea());
#ifdef NOISY_BLOCK_INVALIDATE
printf("%p invalidate (%d, %d, %d, %d)\n",
this, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
if (aLine->IsForceInvalidate())
printf(" dirty line is %p\n", static_cast<void*>(aLine.get());
#endif
Invalidate(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,
line_iterator aLine,
nsIFrame*& aFrameResult)
{
aFrameResult = nsnull;
// First check our remaining lines
if (end_lines() != aLine.next()) {
#ifdef DEBUG
PRBool retry =
#endif
PullFrameFrom(aState, aLine, this, PR_FALSE, aLine.next(), aFrameResult);
NS_ASSERTION(!retry, "Shouldn't have to retry in the current block");
return NS_OK;
}
NS_ASSERTION(!GetOverflowLines(),
"Our overflow lines should have been removed at the start of reflow");
// Try each next in flows
nsBlockFrame* nextInFlow = aState.mNextInFlow;
while (nextInFlow) {
// first normal lines, then overflow lines
if (!nextInFlow->mLines.empty()) {
if (PullFrameFrom(aState, aLine, nextInFlow, PR_FALSE,
nextInFlow->mLines.begin(), aFrameResult)) {
// try again with the same value of nextInFlow
continue;
}
break;
}
nsLineList* overflowLines = nextInFlow->GetOverflowLines();
if (overflowLines) {
if (PullFrameFrom(aState, aLine, nextInFlow, PR_TRUE,
overflowLines->begin(), aFrameResult)) {
// try again with the same value of nextInFlow
continue;
}
break;
}
nextInFlow = (nsBlockFrame*) nextInFlow->GetNextInFlow();
aState.mNextInFlow = nextInFlow;
}
return NS_OK;
}
/**
* Try to pull a frame out of a line pointed at by aFromLine. 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 float from the
* line's float 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.
*
* @return PR_TRUE to force retrying of the pull.
*/
PRBool
nsBlockFrame::PullFrameFrom(nsBlockReflowState& aState,
nsLineBox* aLine,
nsBlockFrame* aFromContainer,
PRBool aFromOverflowLine,
nsLineList::iterator aFromLine,
nsIFrame*& aFrameResult)
{
nsLineBox* fromLine = aFromLine;
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");
NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->GetStyleDisplay()->IsBlockOutside(),
"Disagreement about whether it's a block or not");
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.
aFrameResult = nsnull;
}
else {
// Take frame from fromLine
nsIFrame* frame = fromLine->mFirstChild;
if (aFromContainer != this) {
if (HandleOverflowPlaceholdersForPulledFrame(aState, frame)) {
// we lost this one, retry
return PR_TRUE;
}
aLine->LastChild()->SetNextSibling(frame);
}
// when aFromContainer is 'this', then aLine->LastChild()'s next sibling
// is already set correctly.
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();
fromLine->mFirstChild = frame->GetNextSibling();
}
else {
// Free up the fromLine now that it's empty
// Its bounds might need to be redrawn, though.
// XXX WHY do we invalidate the bounds AND the combined area? doesn't
// the combined area always enclose the bounds?
Invalidate(fromLine->mBounds);
nsLineList* fromLineList = aFromOverflowLine
? aFromContainer->RemoveOverflowLines()
: &aFromContainer->mLines;
if (aFromLine.next() != fromLineList->end())
aFromLine.next()->MarkPreviousMarginDirty();
Invalidate(fromLine->GetCombinedArea());
fromLineList->erase(aFromLine);
// Note that aFromLine just got incremented, so don't use it again here!
aState.FreeLineBox(fromLine);
// Put any remaining overflow lines back.
if (aFromOverflowLine && !fromLineList->empty()) {
aFromContainer->SetOverflowLines(fromLineList);
}
}
// Change geometric parents
if (aFromContainer != this) {
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
NS_ASSERTION(frame->GetParent() == aFromContainer, "unexpected parent frame");
ReparentFrame(frame, aFromContainer, this);
// The frame is being pulled from a next-in-flow; therefore we
// need to add it to our sibling list.
frame->SetNextSibling(nsnull);
if (nsnull != aState.mPrevChild) {
aState.mPrevChild->SetNextSibling(frame);
}
// The frame might have (or contain) floats that need to be
// brought over too.
ReparentFloats(frame, aFromContainer, aFromOverflowLine, PR_TRUE);
}
// Stop pulling because we found a frame to pull
aFrameResult = frame;
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
return PR_FALSE;
}
static void
PlaceFrameView(nsIFrame* aFrame)
{
if (aFrame->HasView())
nsContainerFrame::PositionFrameView(aFrame);
else
nsContainerFrame::PositionChildViews(aFrame);
}
void
nsBlockFrame::SlideLine(nsBlockReflowState& aState,
nsLineBox* aLine, nscoord aDY)
{
NS_PRECONDITION(aDY != 0, "why slide a line nowhere?");
Invalidate(aLine->GetCombinedArea());
// Adjust line state
aLine->SlideBy(aDY);
Invalidate(aLine->GetCombinedArea());
// Adjust the frames in the line
nsIFrame* kid = aLine->mFirstChild;
if (!kid) {
return;
}
if (aLine->IsBlock()) {
if (aDY) {
nsPoint p = kid->GetPosition();
p.y += aDY;
kid->SetPosition(p);
}
// Make sure the frame's view and any child views are updated
::PlaceFrameView(kid);
}
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
PRInt32 n = aLine->GetChildCount();
while (--n >= 0) {
if (aDY) {
nsPoint p = kid->GetPosition();
p.y += aDY;
kid->SetPosition(p);
}
// Make sure the frame's view and any child views are updated
::PlaceFrameView(kid);
kid = kid->GetNextSibling();
}
}
}
NS_IMETHODIMP
nsBlockFrame::AttributeChanged(PRInt32 aNameSpaceID,
nsIAtom* aAttribute,
PRInt32 aModType)
{
nsresult rv = nsBlockFrameSuper::AttributeChanged(aNameSpaceID,
aAttribute, aModType);
if (NS_FAILED(rv)) {
return rv;
}
if (nsGkAtoms::start == aAttribute) {
nsPresContext* presContext = PresContext();
// XXX Not sure if this is necessary anymore
if (RenumberLists(presContext)) {
presContext->PresShell()->
FrameNeedsReflow(this, nsIPresShell::eStyleChange,
NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
else if (nsGkAtoms::value == aAttribute) {
const nsStyleDisplay* styleDisplay = GetStyleDisplay();
if (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) {
// Search for the closest ancestor that's a block frame. We
// make the assumption that all related list items share a
// common block parent.
// XXXldb I think that's a bad assumption.
nsBlockFrame* blockParent = nsLayoutUtils::FindNearestBlockAncestor(this);
// Tell the enclosing block frame to renumber list items within
// itself
if (nsnull != blockParent) {
nsPresContext* presContext = PresContext();
// XXX Not sure if this is necessary anymore
if (blockParent->RenumberLists(presContext)) {
presContext->PresShell()->
FrameNeedsReflow(blockParent, nsIPresShell::eStyleChange,
NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
}
return rv;
}
inline PRBool
IsPaddingZero(nsStyleUnit aUnit, nsStyleCoord &aCoord)
{
return ((aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
(aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
}
inline PRBool
IsMarginZero(nsStyleUnit aUnit, nsStyleCoord &aCoord)
{
return (aUnit == eStyleUnit_Auto ||
(aUnit == eStyleUnit_Coord && aCoord.GetCoordValue() == 0) ||
(aUnit == eStyleUnit_Percent && aCoord.GetPercentValue() == 0.0));
}
/* virtual */ PRBool
nsBlockFrame::IsSelfEmpty()
{
const nsStylePosition* position = GetStylePosition();
switch (position->mMinHeight.GetUnit()) {
case eStyleUnit_Coord:
if (position->mMinHeight.GetCoordValue() != 0)
return PR_FALSE;
break;
case eStyleUnit_Percent:
if (position->mMinHeight.GetPercentValue() != 0.0f)
return PR_FALSE;
break;
default:
return PR_FALSE;
}
switch (position->mHeight.GetUnit()) {
case eStyleUnit_Auto:
break;
case eStyleUnit_Coord:
if (position->mHeight.GetCoordValue() != 0)
return PR_FALSE;
break;
case eStyleUnit_Percent:
if (position->mHeight.GetPercentValue() != 0.0f)
return PR_FALSE;
break;
default:
return PR_FALSE;
}
const nsStyleBorder* border = GetStyleBorder();
const nsStylePadding* padding = GetStylePadding();
nsStyleCoord coord;
if (border->GetBorderWidth(NS_SIDE_TOP) != 0 ||
border->GetBorderWidth(NS_SIDE_BOTTOM) != 0 ||
!IsPaddingZero(padding->mPadding.GetTopUnit(),
padding->mPadding.GetTop(coord)) ||
!IsPaddingZero(padding->mPadding.GetBottomUnit(),
padding->mPadding.GetBottom(coord))) {
return PR_FALSE;
}
return PR_TRUE;
}
PRBool
nsBlockFrame::CachedIsEmpty()
{
if (!IsSelfEmpty()) {
return PR_FALSE;
}
for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line)
{
if (!line->CachedIsEmpty())
return PR_FALSE;
}
return PR_TRUE;
}
PRBool
nsBlockFrame::IsEmpty()
{
if (!IsSelfEmpty()) {
return PR_FALSE;
}
for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end;
++line)
{
if (!line->IsEmpty())
return PR_FALSE;
}
return PR_TRUE;
}
PRBool
nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState,
nsLineBox* aLine)
{
if (aState.GetFlag(BRS_APPLYTOPMARGIN)) {
// Apply short-circuit check to avoid searching the line list
return PR_TRUE;
}
if (!aState.IsAdjacentWithTop()) {
// If we aren't at the top Y coordinate then something of non-zero
// height must have been placed. Therefore the childs top-margin
// applies.
aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE);
return PR_TRUE;
}
// Determine if this line is "essentially" the first line
line_iterator line = begin_lines();
if (aState.GetFlag(BRS_HAVELINEADJACENTTOTOP)) {
line = aState.mLineAdjacentToTop;
}
while (line != aLine) {
if (!line->CachedIsEmpty() || line->HasClearance()) {
// A line which precedes aLine is non-empty, or has clearance,
// so therefore the top margin applies.
aState.SetFlag(BRS_APPLYTOPMARGIN, PR_TRUE);
return PR_TRUE;
}
// No need to apply the top margin if the line has floats. We
// should collapse anyway (bug 44419)
++line;
aState.SetFlag(BRS_HAVELINEADJACENTTOTOP, PR_TRUE);
aState.mLineAdjacentToTop = line;
}
// 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(nsPresContext* aPresContext)
{
if (mLines.empty())
return nsnull;
nsLineBox *firstLine = mLines.front();
if (firstLine->IsBlock())
return firstLine->mFirstChild;
if (!firstLine->CachedIsEmpty())
return nsnull;
line_iterator secondLine = begin_lines();
++secondLine;
if (secondLine == end_lines() || !secondLine->IsBlock())
return nsnull;
return secondLine->mFirstChild;
}
// If placeholders/floats split during reflowing a line, but that line will
// be put on the next page, then put the placeholders/floats back the way
// they were before the line was reflowed.
void
nsBlockFrame::UndoSplitPlaceholders(nsBlockReflowState& aState,
nsIFrame* aLastPlaceholder)
{
nsIFrame* undoPlaceholder;
if (aLastPlaceholder) {
undoPlaceholder = aLastPlaceholder->GetNextSibling();
aLastPlaceholder->SetNextSibling(nsnull);
}
else {
undoPlaceholder = aState.mOverflowPlaceholders.FirstChild();
aState.mOverflowPlaceholders.SetFrames(nsnull);
}
// remove the next in flows of the placeholders that need to be removed
for (nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(undoPlaceholder);
placeholder; ) {
NS_ASSERTION(!placeholder->GetNextInFlow(), "Must be the last placeholder");
nsFrameManager* fm = aState.mPresContext->GetPresShell()->FrameManager();
fm->UnregisterPlaceholderFrame(placeholder);
placeholder->SetOutOfFlowFrame(nsnull);
// XXX we probably should be doing something with oof here. But what?
nsSplittableFrame::RemoveFromFlow(placeholder);
nsIFrame* savePlaceholder = placeholder;
placeholder = static_cast<nsPlaceholderFrame*>(placeholder->GetNextSibling());
savePlaceholder->Destroy();
}
}
nsresult
nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
line_iterator aLine,
PRBool* aKeepReflowGoing)
{
NS_PRECONDITION(*aKeepReflowGoing, "bad caller");
nsresult rv = NS_OK;
nsIFrame* frame = aLine->mFirstChild;
if (!frame) {
NS_ASSERTION(PR_FALSE, "program error - unexpected empty line");
return NS_ERROR_NULL_POINTER;
}
// Prepare the block reflow engine
const nsStyleDisplay* display = frame->GetStyleDisplay();
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState);
PRUint8 breakType = display->mBreakType;
// If a float split and its prev-in-flow was followed by a <BR>, then combine
// the <BR>'s break type with the block's break type (the block will be the very
// next frame after the split float).
if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) {
breakType = nsLayoutUtils::CombineBreakType(breakType,
aState.mFloatBreakType);
aState.mFloatBreakType = NS_STYLE_CLEAR_NONE;
}
// Clear past floats before the block if the clear style is not none
aLine->SetBreakTypeBefore(breakType);
// 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 it's not significant. Otherwise, dig
// deeper.
PRBool applyTopMargin =
!frame->GetPrevInFlow() && ShouldApplyTopMargin(aState, aLine);
if (applyTopMargin) {
// The HasClearance setting is only valid if ShouldApplyTopMargin
// returned PR_FALSE (in which case the top-margin-root set our
// clearance flag). Otherwise clear it now. We'll set it later on
// ourselves if necessary.
aLine->ClearHasClearance();
}
PRBool treatWithClearance = aLine->HasClearance();
// If our top margin was counted as part of some parents top-margin
// collapse and we are being speculatively reflowed assuming this
// frame DID NOT need clearance, then we need to check that
// assumption.
if (!treatWithClearance && !applyTopMargin && breakType != NS_STYLE_CLEAR_NONE &&
aState.mReflowState.mDiscoveredClearance) {
nscoord curY = aState.mY + aState.mPrevBottomMargin.get();
nscoord clearY = aState.ClearFloats(curY, breakType);
if (clearY != curY) {
// Looks like that assumption was invalid, we do need
// clearance. Tell our ancestor so it can reflow again. It is
// responsible for actually setting our clearance flag before
// the next reflow.
treatWithClearance = PR_TRUE;
// Only record the first frame that requires clearance
if (!*aState.mReflowState.mDiscoveredClearance) {
*aState.mReflowState.mDiscoveredClearance = frame;
}
// Exactly what we do now is flexible since we'll definitely be
// reflowed.
}
}
if (treatWithClearance) {
applyTopMargin = PR_TRUE;
}
nsIFrame* clearanceFrame = nsnull;
nscoord startingY = aState.mY;
nsCollapsingMargin incomingMargin = aState.mPrevBottomMargin;
nscoord clearance;
// Save the original position of the frame so that we can reposition
// its view as needed.
nsPoint originalPosition = frame->GetPosition();
while (PR_TRUE) {
// Save the frame's current position. We might need it later.
nscoord passOriginalY = frame->GetRect().y;
clearance = 0;
nscoord topMargin = 0;
PRBool mayNeedRetry = PR_FALSE;
if (applyTopMargin) {
// Precompute the blocks top margin value so that we can get the
// correct available space (there might be a float that's
// already been placed below the aState.mPrevBottomMargin
// Setup a reflowState to get the style computed margin-top
// value. We'll use a reason of `resize' so that we don't fudge
// any incremental reflow state.
// 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.
// XXX building a complete nsHTMLReflowState just to get the margin-top
// seems like a waste. And we do this for almost every block!
nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE);
nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState,
frame, availSpace);
if (treatWithClearance) {
aState.mY += aState.mPrevBottomMargin.get();
aState.mPrevBottomMargin.Zero();
}
// Now compute the collapsed margin-top value into aState.mPrevBottomMargin, assuming
// that all child margins collapse down to clearanceFrame.
nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState,
&aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry);
// XXX optimization; we could check the collapsing children to see if they are sure
// to require clearance, and so avoid retrying them
if (clearanceFrame) {
// Don't allow retries on the second pass. The clearance decisions for the
// blocks whose top-margins collapse with ours are now fixed.
mayNeedRetry = PR_FALSE;
}
if (!treatWithClearance && !clearanceFrame && breakType != NS_STYLE_CLEAR_NONE) {
// We don't know if we need clearance and this is the first,
// optimistic pass. So determine whether *this block* needs
// clearance. Note that we do not allow the decision for whether
// this block has clearance to change on the second pass; that
// decision is only allowed to be made under the optimistic
// first pass.
nscoord curY = aState.mY + aState.mPrevBottomMargin.get();
nscoord clearY = aState.ClearFloats(curY, breakType);
if (clearY != curY) {
// Looks like we need clearance and we didn't know about it already. So
// recompute collapsed margin
treatWithClearance = PR_TRUE;
// Remember this decision, needed for incremental reflow
aLine->SetHasClearance();
// Apply incoming margins
aState.mY += aState.mPrevBottomMargin.get();
aState.mPrevBottomMargin.Zero();
// Compute the collapsed margin again, ignoring the incoming margin this time
mayNeedRetry = PR_FALSE;
nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState,
&aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry);
}
}
// Temporarily advance the running Y value so that the
// GetAvailableSpace method will return the right available
// space. This undone as soon as the horizontal margins are
// computed.
topMargin = aState.mPrevBottomMargin.get();
if (treatWithClearance) {
nscoord currentY = aState.mY;
// advance mY to the clear position.
aState.mY = aState.ClearFloats(aState.mY, breakType);
// Compute clearance. It's the amount we need to add to the top
// border-edge of the frame, after applying collapsed margins
// from the frame and its children, to get it to line up with
// the bottom of the floats. The former is currentY + topMargin,
// the latter is the current aState.mY.
// Note that negative clearance is possible
clearance = aState.mY - (currentY + topMargin);
// Add clearance to our top margin while we compute available
// space for the frame
topMargin += clearance;
// Note that aState.mY should stay where it is: at the top
// border-edge of the frame
} else {
// Advance aState.mY to the top border-edge of the frame.
aState.mY += topMargin;
}
}
// Here aState.mY is the top border-edge of the block.
// Compute the available space for the block
aState.GetAvailableSpace();
#ifdef REALLY_NOISY_REFLOW
printf("setting line %p isImpacted to %s\n", aLine.get(), aState.IsImpactedByFloat()?"true":"false");
#endif
PRBool isImpacted = aState.IsImpactedByFloat() ? PR_TRUE : PR_FALSE;
aLine->SetLineIsImpactedByFloat(isImpacted);
nsRect availSpace;
aState.ComputeBlockAvailSpace(frame, display, availSpace);
// Now put the Y coordinate back to the top of the top-margin +
// clearance, and flow the block.
aState.mY -= topMargin;
availSpace.y -= topMargin;
if (NS_UNCONSTRAINEDSIZE != availSpace.height) {
availSpace.height += topMargin;
}
// keep track of the last overflow float in case we need to undo any new additions
nsIFrame* lastPlaceholder = aState.mOverflowPlaceholders.LastChild();
// Reflow the block into the available space
nsMargin computedOffsets;
// construct the html reflow state for the block. ReflowBlock
// will initialize it
nsHTMLReflowState blockHtmlRS(aState.mPresContext, aState.mReflowState, frame,
nsSize(availSpace.width, availSpace.height));
blockHtmlRS.mFlags.mHasClearance = aLine->HasClearance();
nsSpaceManager::SavedState spaceManagerState;
if (mayNeedRetry) {
blockHtmlRS.mDiscoveredClearance = &clearanceFrame;
aState.mSpaceManager->PushState(&spaceManagerState);
} else if (!applyTopMargin) {
blockHtmlRS.mDiscoveredClearance = aState.mReflowState.mDiscoveredClearance;
}
nsReflowStatus frameReflowStatus = NS_FRAME_COMPLETE;
rv = brc.ReflowBlock(availSpace, applyTopMargin, aState.mPrevBottomMargin,
clearance, aState.IsAdjacentWithTop(), computedOffsets,
aLine.get(), blockHtmlRS, frameReflowStatus);
// If this was a second-pass reflow and the block's vertical position
// changed, invalidates from the first pass might have happened in the
// wrong places. Invalidate the entire overflow rect at the new position.
if (!mayNeedRetry && clearanceFrame &&
frame->GetRect().y != passOriginalY) {
Invalidate(frame->GetOverflowRect() + frame->GetPosition());
}
NS_ENSURE_SUCCESS(rv, rv);
if (mayNeedRetry && clearanceFrame) {
UndoSplitPlaceholders(aState, lastPlaceholder);
aState.mSpaceManager->PopState(&spaceManagerState);
aState.mY = startingY;
aState.mPrevBottomMargin = incomingMargin;
continue;
}
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.
UndoSplitPlaceholders(aState, lastPlaceholder);
PushLines(aState, aLine.prev());
*aKeepReflowGoing = PR_FALSE;
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
}
else {
// Note: line-break-after a block is a nop
// Try to place the child block.
// Don't force the block to fit if we have positive clearance, because
// pushing it to the next page would give it more room.
// Don't force the block to fit if it's impacted by a float. If it is,
// then pushing it to the next page would give it more room. Note that
// isImpacted doesn't include impact from the block's own floats.
PRBool forceFit = aState.IsAdjacentWithTop() && clearance <= 0 &&
!isImpacted;
nsCollapsingMargin collapsedBottomMargin;
nsRect combinedArea(0,0,0,0);
*aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, forceFit, aLine.get(),
computedOffsets, collapsedBottomMargin,
aLine->mBounds, combinedArea, frameReflowStatus);
if (aLine->SetCarriedOutBottomMargin(collapsedBottomMargin)) {
line_iterator nextLine = aLine;
++nextLine;
if (nextLine != end_lines()) {
nextLine->MarkPreviousMarginDirty();
}
}
aLine->SetCombinedArea(combinedArea);
if (*aKeepReflowGoing) {
// Some of the child block fit
// Advance to new Y position
nscoord newY = aLine->mBounds.YMost();
aState.mY = newY;
// Continue the block frame now if it didn't completely fit in
// the available space.
if (!NS_FRAME_IS_FULLY_COMPLETE(frameReflowStatus)) {
PRBool madeContinuation;
rv = CreateContinuationFor(aState, nsnull, frame, madeContinuation);
NS_ENSURE_SUCCESS(rv, rv);
nsIFrame* nextFrame = frame->GetNextInFlow();
NS_ASSERTION(nextFrame, "We're supposed to have a next-in-flow by now");
if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
// If nextFrame used to be an overflow container, make it a normal block
if (!madeContinuation &&
(NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) {
aState.mOverflowTracker.Finish(frame);
nsContainerFrame* parent =
static_cast<nsContainerFrame*>(nextFrame->GetParent());
rv = parent->StealFrame(aState.mPresContext, nextFrame);
NS_ENSURE_SUCCESS(rv, rv);
ReparentFrame(nextFrame, parent, this);
nextFrame->SetNextSibling(frame->GetNextSibling());
frame->SetNextSibling(nextFrame);
madeContinuation = PR_TRUE; // needs to be added to mLines
nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
frameReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
}
// Push continuation to a new line, but only if we actually made one.
if (madeContinuation) {
nsLineBox* line = aState.NewLineBox(nextFrame, 1, PR_TRUE);
NS_ENSURE_TRUE(line, NS_ERROR_OUT_OF_MEMORY);
mLines.after_insert(aLine, line);
}
PushLines(aState, aLine);
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
// If we need to reflow the continuation of the block child,
// then we'd better reflow our continuation
if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) {
aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
// We also need to make that continuation's line dirty so
// it gets reflowed when we reflow our next in flow. The
// nif's line must always be either a line of the nif's
// parent block (only if we didn't make a continuation) or
// else one of our own overflow lines. In the latter case
// the line is already marked dirty, so just handle the
// first case.
if (!madeContinuation) {
nsBlockFrame* nifBlock = static_cast<nsBlockFrame*>(nextFrame->GetParent());
NS_ASSERTION(nifBlock->GetType() == nsGkAtoms::blockFrame
|| nifBlock->GetType() == nsGkAtoms::areaFrame,
"A block's child's next in flow's parent must be a block!");
for (line_iterator line = nifBlock->begin_lines(),
line_end = nifBlock->end_lines(); line != line_end; ++line) {
if (line->Contains(nextFrame)) {
line->MarkDirty();
break;
}
}
}
}
*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.Zero();
}
else { // frame is complete but its overflow is not complete
// Disconnect the next-in-flow and put it in our overflow tracker
if (!madeContinuation &&
!(NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) {
// It already exists, but as a normal next-in-flow, so we need
// to dig it out of the child lists.
nsContainerFrame* parent = static_cast<nsContainerFrame*>
(nextFrame->GetParent());
rv = parent->StealFrame(aState.mPresContext, nextFrame);
NS_ENSURE_SUCCESS(rv, rv);
}
else if (madeContinuation) {
frame->SetNextSibling(nextFrame->GetNextSibling());
nextFrame->SetNextSibling(nsnull);
}
// Put it in our overflow list
aState.mOverflowTracker.Insert(nextFrame, frameReflowStatus);
aState.mReflowStatus = NS_FRAME_MERGE_INCOMPLETE(frameReflowStatus,
aState.mReflowStatus);
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
printf(": reflow complete but overflow incomplete for ");
nsFrame::ListTag(stdout, frame);
printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n",
aState.mPrevBottomMargin, collapsedBottomMargin.get());
#endif
aState.mPrevBottomMargin = collapsedBottomMargin;
}
}
else { // frame is fully complete
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
printf(": reflow complete for ");
nsFrame::ListTag(stdout, frame);
printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n",
aState.mPrevBottomMargin, collapsedBottomMargin.get());
#endif
aState.mPrevBottomMargin = collapsedBottomMargin;
}
#ifdef NOISY_VERTICAL_MARGINS
ListTag(stdout);
printf(": frame=");
nsFrame::ListTag(stdout, frame);
printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n",
brc.GetCarriedOutBottomMargin(), collapsedBottomMargin.get(),
aState.mPrevBottomMargin);
#endif
}
else {
// None of the block fits. Determine the correct reflow status.
if (aLine == mLines.front()) {
// 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.
UndoSplitPlaceholders(aState, lastPlaceholder);
PushLines(aState, aLine.prev());
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
}
}
}
break; // out of the reflow retry loop
}
// Now that we've got its final position all figured out, position any child
// views it may have. Note that the case when frame has a view got handled
// by FinishReflowChild, but that function didn't have the coordinates needed
// to correctly decide whether to reposition child views.
if (originalPosition != frame->GetPosition() && !frame->HasView()) {
nsContainerFrame::PositionChildViews(frame);
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
return rv;
}
nsresult
nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState,
line_iterator aLine,
PRBool* aKeepReflowGoing)
{
nsresult rv = NS_OK;
*aKeepReflowGoing = PR_TRUE;
#ifdef DEBUG
PRInt32 spins = 0;
#endif
LineReflowStatus lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
PRBool movedPastFloat = PR_FALSE;
do {
PRBool allowPullUp = PR_TRUE;
nsIContent* forceBreakInContent = nsnull;
PRInt32 forceBreakOffset = -1;
do {
nsSpaceManager::SavedState spaceManagerState;
aState.mReflowState.mSpaceManager->PushState(&spaceManagerState);
// Once upon a time we allocated the first 30 nsLineLayout objects
// on the stack, and then we switched to the heap. At that time
// these objects were large (1100 bytes on a 32 bit system).
// Then the nsLineLayout object was shrunk to 156 bytes by
// removing some internal buffers. Given that it is so much
// smaller, the complexity of 2 different ways of allocating
// no longer makes sense. Now we always allocate on the stack.
nsLineLayout lineLayout(aState.mPresContext,
aState.mReflowState.mSpaceManager,
&aState.mReflowState, &aLine);
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
if (forceBreakInContent) {
lineLayout.ForceBreakAtPosition(forceBreakInContent, forceBreakOffset);
}
rv = DoReflowInlineFrames(aState, lineLayout, aLine,
aKeepReflowGoing, &lineReflowStatus,
allowPullUp);
lineLayout.EndLineReflow();
if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus ||
LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
if (lineLayout.NeedsBackup()) {
NS_ASSERTION(!forceBreakInContent, "Backing up twice; this should never be necessary");
// If there is no saved break position, then this will set
// set forceBreakInContent to null and we won't back up, which is
// correct.
forceBreakInContent = lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset);
} else {
forceBreakInContent = nsnull;
}
// restore the space manager state
aState.mReflowState.mSpaceManager->PopState(&spaceManagerState);
// Clear out float lists
aState.mCurrentLineFloats.DeleteAll();
aState.mBelowCurrentLineFloats.DeleteAll();
}
#ifdef DEBUG
spins++;
if (1000 == spins) {
ListTag(stdout);
printf(": yikes! spinning on a line over 1000 times!\n");
NS_ABORT();
}
#endif
// Don't allow pullup on a subsequent LINE_REFLOW_REDO_NO_PULL pass
allowPullUp = PR_FALSE;
} while (NS_SUCCEEDED(rv) && LINE_REFLOW_REDO_NO_PULL == lineReflowStatus);
if (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
movedPastFloat = PR_TRUE;
}
} while (NS_SUCCEEDED(rv) && LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus);
// If we did at least one REDO_FOR_FLOAT, then the line did not fit next to some float.
// Mark it as impacted by a float, even if it no longer is next to a float.
if (movedPastFloat) {
aLine->SetLineIsImpactedByFloat(PR_TRUE);
}
return rv;
}
// If at least one float on the line was complete, not at the top of
// page, but was truncated, then restore the overflow floats to what
// they were before and push the line. The floats that will be removed
// from the list aren't yet known by the block's next in flow.
void
nsBlockFrame::PushTruncatedPlaceholderLine(nsBlockReflowState& aState,
line_iterator aLine,
nsIFrame* aLastPlaceholder,
PRBool& aKeepReflowGoing)
{
UndoSplitPlaceholders(aState, aLastPlaceholder);
line_iterator prevLine = aLine;
--prevLine;
PushLines(aState, prevLine);
aKeepReflowGoing = PR_FALSE;
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
}
#ifdef DEBUG
static const char* LineReflowStatusNames[] = {
"LINE_REFLOW_OK", "LINE_REFLOW_STOP", "LINE_REFLOW_REDO_NO_PULL",
"LINE_REFLOW_REDO_NEXT_BAND", "LINE_REFLOW_TRUNCATED"
};
#endif
nsresult
nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
PRBool* aKeepReflowGoing,
LineReflowStatus* aLineReflowStatus,
PRBool aAllowPullUp)
{
// Forget all of the floats on the line
aLine->FreeFloats(aState.mFloatCacheFreeList);
aState.mFloatCombinedArea.SetRect(0, 0, 0, 0);
// Setup initial coordinate system for reflowing the inline frames
// into. Apply a previous block frame's bottom margin first.
if (ShouldApplyTopMargin(aState, aLine)) {
aState.mY += aState.mPrevBottomMargin.get();
}
aState.GetAvailableSpace();
PRBool impactedByFloats = aState.IsImpactedByFloat() ? PR_TRUE : PR_FALSE;
aLine->SetLineIsImpactedByFloat(impactedByFloats);
#ifdef REALLY_NOISY_REFLOW
printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n",
this, impactedByFloats);
#endif
const nsMargin& borderPadding = aState.BorderPadding();
nscoord x = aState.mAvailSpaceRect.x + borderPadding.left;
nscoord availWidth = aState.mAvailSpaceRect.width;
nscoord availHeight;
if (aState.GetFlag(BRS_UNCONSTRAINEDHEIGHT)) {
availHeight = NS_UNCONSTRAINEDSIZE;
}
else {
/* XXX get the height right! */
availHeight = aState.mAvailSpaceRect.height;
}
// Make sure to enable resize optimization before we call BeginLineReflow
// because it might get disabled there
aLine->EnableResizeReflowOptimization();
aLineLayout.BeginLineReflow(x, aState.mY,
availWidth, availHeight,
impactedByFloats,
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);
}
// keep track of the last overflow float in case we need to undo any new additions
nsIFrame* lastPlaceholder = aState.mOverflowPlaceholders.LastChild();
// Reflow the frames that are already on the line first
nsresult rv = NS_OK;
LineReflowStatus lineReflowStatus = LINE_REFLOW_OK;
PRInt32 i;
nsIFrame* frame = aLine->mFirstChild;
// Determine whether this is a line of placeholders for out-of-flow
// continuations
PRBool isContinuingPlaceholders = PR_FALSE;
if (impactedByFloats) {
// There is a soft break opportunity at the start of the line, because
// we can always move this line down below float(s).
if (aLineLayout.NotifyOptionalBreakPosition(frame->GetContent(), 0, PR_TRUE)) {
lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
}
}
// need to repeatedly call GetChildCount here, because the child
// count can change during the loop!
for (i = 0; LINE_REFLOW_OK == lineReflowStatus && i < aLine->GetChildCount();
i++, frame = frame->GetNextSibling()) {
if (IsContinuationPlaceholder(frame)) {
isContinuingPlaceholders = PR_TRUE;
}
rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame,
&lineReflowStatus);
NS_ENSURE_SUCCESS(rv, rv);
if (LINE_REFLOW_OK != lineReflowStatus) {
// It is possible that one or more of next lines are empty
// (because of DeleteNextInFlowChild). If so, delete them now
// in case we are finished.
++aLine;
while ((aLine != end_lines()) && (0 == aLine->GetChildCount())) {
// XXX Is this still necessary now that DeleteNextInFlowChild
// uses DoRemoveFrame?
nsLineBox *toremove = aLine;
aLine = mLines.erase(aLine);
NS_ASSERTION(nsnull == toremove->mFirstChild, "bad empty line");
aState.FreeLineBox(toremove);
}
--aLine;
if (LINE_REFLOW_TRUNCATED == lineReflowStatus) {
// Push the line with the truncated float
PushTruncatedPlaceholderLine(aState, aLine, lastPlaceholder, *aKeepReflowGoing);
}
}
}
// Don't pull up new frames into lines with continuation placeholders
if (!isContinuingPlaceholders && aAllowPullUp) {
// Pull frames and reflow them until we can't
while (LINE_REFLOW_OK == lineReflowStatus) {
rv = PullFrame(aState, aLine, frame);
NS_ENSURE_SUCCESS(rv, rv);
if (nsnull == frame) {
break;
}
while (LINE_REFLOW_OK == lineReflowStatus) {
PRInt32 oldCount = aLine->GetChildCount();
rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame,
&lineReflowStatus);
NS_ENSURE_SUCCESS(rv, 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 = frame->GetNextSibling();
}
else {
break;
}
}
}
}
// We only need to backup if the line isn't going to be reflowed again anyway
PRBool needsBackup = aLineLayout.NeedsBackup() &&
(lineReflowStatus == LINE_REFLOW_STOP || lineReflowStatus == LINE_REFLOW_OK);
if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
NS_WARNING("We shouldn't be backing up more than once! "
"Someone must have set a break opportunity beyond the available width, "
"even though there were better break opportunities before it");
needsBackup = PR_FALSE;
}
if (needsBackup) {
// We need to try backing up to before a text run
PRInt32 offset;
nsIContent* breakContent = aLineLayout.GetLastOptionalBreakPosition(&offset);
// XXX It's possible, in fact not unusual, for the break opportunity to already
// be the end of the line. We should detect that and optimize to not
// re-do the line.
if (breakContent) {
// We can back up!
lineReflowStatus = LINE_REFLOW_REDO_NO_PULL;
}
} else {
// In case we reflow this line again, remember that we don't
// need to force any breaking
aLineLayout.ClearOptionalBreakPosition();
}
if (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) {
// This happens only when we have a line that is impacted by
// floats and the first element in the line doesn't fit with
// the floats.
//
// What we do is to advance past the first float we find and
// then reflow the line all over again.
NS_ASSERTION(aState.IsImpactedByFloat(),
"redo line on totally empty line");
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mAvailSpaceRect.height,
"unconstrained height on totally empty line");
if (aState.mAvailSpaceRect.height > 0) {
aState.mY += aState.mAvailSpaceRect.height;
} else {
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mReflowState.availableHeight,
"We shouldn't be running out of height here");
if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.availableHeight) {
// just move it down a bit to try to get out of this mess
aState.mY += 1;
} else {
// There's nowhere to retry placing the line. Just treat it as if
// we placed the float but it was truncated so we need this line
// to go to the next page/column.
lineReflowStatus = LINE_REFLOW_TRUNCATED;
// Push the line that didn't fit
PushTruncatedPlaceholderLine(aState, aLine, lastPlaceholder,
*aKeepReflowGoing);
}
}
// We don't want to advance by the bottom margin anymore (we did it
// once at the beginning of this function, which will just be called
// again), and we certainly don't want to go back if it's negative
// (infinite loop, bug 153429).
aState.mPrevBottomMargin.Zero();
// 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 float.
}
else if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus) {
// We don't want to advance by the bottom margin anymore (we did it
// once at the beginning of this function, which will just be called
// again), and we certainly don't want to go back if it's negative
// (infinite loop, bug 153429).
aState.mPrevBottomMargin.Zero();
}
else if (LINE_REFLOW_TRUNCATED != lineReflowStatus) {
// If we are propagating out a break-before status then there is
// no point in placing the line.
if (!NS_INLINE_IS_BREAK_BEFORE(aState.mReflowStatus)) {
if (PlaceLine(aState, aLineLayout, aLine, aKeepReflowGoing)) {
UndoSplitPlaceholders(aState, lastPlaceholder); // undo since we pushed the current line
}
}
}
#ifdef DEBUG
if (gNoisyReflow) {
printf("Line reflow status = %s\n", LineReflowStatusNames[lineReflowStatus]);
}
#endif
*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,
line_iterator aLine,
nsIFrame* aFrame,
LineReflowStatus* aLineReflowStatus)
{
NS_ENSURE_ARG_POINTER(aFrame);
*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
// Reflow the inline frame
nsReflowStatus frameReflowStatus;
PRBool pushedFrame;
nsresult rv = aLineLayout.ReflowFrame(aFrame, frameReflowStatus,
nsnull, pushedFrame);
NS_ENSURE_SUCCESS(rv, rv);
if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) {
// we need to ensure that the frame's nextinflow gets reflowed.
aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
nsBlockFrame* ourNext = static_cast<nsBlockFrame*>(GetNextInFlow());
if (ourNext && aFrame->GetNextInFlow()) {
line_iterator f = ourNext->FindLineFor(aFrame->GetNextInFlow());
if (f != ourNext->end_lines()) {
f->MarkDirty();
}
}
}
NS_ENSURE_SUCCESS(rv, 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;
/* XXX
This is where we need to add logic to handle some odd behavior.
For one thing, we should usually place at least one thing next
to a left float, even when that float takes up all the width on a line.
see bug 22496
*/
// Process the child frames reflow status. There are 5 cases:
// complete, not-complete, break-before, break-after-complete,
// break-after-not-complete. There are two situations: we are a
// block or we are an inline. This makes a total of 10 cases
// (fortunately, there is some overlap).
aLine->SetBreakTypeAfter(NS_STYLE_CLEAR_NONE);
if (NS_INLINE_IS_BREAK(frameReflowStatus) ||
(NS_STYLE_CLEAR_NONE != aState.mFloatBreakType)) {
// 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((NS_STYLE_CLEAR_NONE != breakType) ||
(NS_STYLE_CLEAR_NONE != aState.mFloatBreakType), "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 there's no room (e.g. on a
// line with wide floats). Inform the caller to reflow the
// line after skipping past a float.
*aLineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND;
}
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, aLineReflowStatus);
NS_ENSURE_SUCCESS(rv, 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 {
// If a float split and its prev-in-flow was followed by a <BR>, then combine
// the <BR>'s break type with the inline's break type (the inline will be the very
// next frame after the split float).
if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) {
breakType = nsLayoutUtils::CombineBreakType(breakType,
aState.mFloatBreakType);
aState.mFloatBreakType = NS_STYLE_CLEAR_NONE;
}
// Break-after cases
if (breakType == NS_STYLE_CLEAR_LINE) {
if (!aLineLayout.GetLineEndsInBR()) {
breakType = NS_STYLE_CLEAR_NONE;
}
}
aLine->SetBreakTypeAfter(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);
NS_ENSURE_SUCCESS(rv, rv);
if (!aLineLayout.GetLineEndsInBR()) {
// Remember that the line has wrapped
aLine->SetLineWrapped(PR_TRUE);
}
}
// Split line, but after the frame just reflowed
rv = SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus) ||
(NS_INLINE_IS_BREAK_AFTER(frameReflowStatus) &&
!aLineLayout.GetLineEndsInBR())) {
// Mark next line dirty in case SplitLine didn't end up
// pushing any frames.
nsLineList_iterator next = aLine.next();
if (next != end_lines() && !next->IsBlock()) {
next->MarkDirty();
}
}
}
}
else if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) {
// Frame is not-complete, no special breaking status
nsIAtom* frameType = aFrame->GetType();
// Create a continuation for the incomplete frame. Note that the
// frame may already have a continuation.
PRBool madeContinuation;
rv = (nsGkAtoms::placeholderFrame == frameType)
? SplitPlaceholder(aState, aFrame)
: CreateContinuationFor(aState, aLine, aFrame, madeContinuation);
NS_ENSURE_SUCCESS(rv, rv);
// Remember that the line has wrapped
if (!aLineLayout.GetLineEndsInBR()) {
aLine->SetLineWrapped(PR_TRUE);
}
// If we are reflowing the first letter frame or a placeholder then
// don't split the line and don't stop the line reflow...
PRBool splitLine = !reflowingFirstLetter &&
nsGkAtoms::placeholderFrame != frameType;
if (reflowingFirstLetter) {
if ((nsGkAtoms::inlineFrame == frameType) ||
(nsGkAtoms::lineFrame == frameType)) {
splitLine = PR_TRUE;
}
}
if (splitLine) {
// Split line after the current frame
*aLineReflowStatus = LINE_REFLOW_STOP;
rv = SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus);
NS_ENSURE_SUCCESS(rv, rv);
// Mark next line dirty in case SplitLine didn't end up
// pushing any frames.
nsLineList_iterator next = aLine.next();
if (next != end_lines() && !next->IsBlock()) {
next->MarkDirty();
}
}
}
else if (NS_FRAME_IS_TRUNCATED(frameReflowStatus)) {
// if the frame is a placeholder and was complete but truncated (and not at the top
// of page), the entire line will be pushed to give it another chance to not truncate.
if (nsGkAtoms::placeholderFrame == aFrame->GetType()) {
*aLineReflowStatus = LINE_REFLOW_TRUNCATED;
}
}
return NS_OK;
}
/**
* Create a continuation, if necessary, for aFrame. Place it in aLine
* if aLine is not null. 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);
NS_ENSURE_SUCCESS(rv, rv);
if (nsnull != nextInFlow) {
aMadeNewFrame = PR_TRUE;
if (aLine) {
aLine->SetChildCount(aLine->GetChildCount() + 1);
}
}
#ifdef DEBUG
VerifyLines(PR_FALSE);
#endif
return rv;
}
nsresult
nsBlockFrame::SplitPlaceholder(nsBlockReflowState& aState,
nsIFrame* aPlaceholder)
{
nsIFrame* nextInFlow;
nsresult rv = CreateNextInFlow(aState.mPresContext, this, aPlaceholder, nextInFlow);
NS_ENSURE_SUCCESS(rv, rv);
if (!nextInFlow) {
// Next in flow was not created because it already exists.
return NS_OK;
}
// put the sibling list back to what it was before the continuation was created
nsIFrame *contFrame = aPlaceholder->GetNextSibling();
nsIFrame *next = contFrame->GetNextSibling();
aPlaceholder->SetNextSibling(next);
contFrame->SetNextSibling(nsnull);
NS_ASSERTION(IsContinuationPlaceholder(contFrame),
"Didn't create the right kind of frame");
// The new out of flow frame does not get put anywhere; the out-of-flows
// for placeholders in mOverflowPlaceholders are not kept in any child list
aState.mOverflowPlaceholders.AppendFrame(this, contFrame);
return NS_OK;
}
static nsFloatCache*
GetLastFloat(nsLineBox* aLine)
{
nsFloatCache* fc = aLine->GetFirstFloat();
while (fc && fc->Next()) {
fc = fc->Next();
}
return fc;
}
static PRBool
CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, nsFloatCache* aFC)
{
if (!aFC)
return PR_TRUE;
for (nsIFrame* f = aFC->mPlaceholder; f; f = f->GetParent()) {
if (f->GetParent() == aBlock)
return aLine->Contains(f);
}
NS_ASSERTION(PR_FALSE, "aBlock is not an ancestor of aFrame!");
return PR_TRUE;
}
nsresult
nsBlockFrame::SplitLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
nsIFrame* aFrame,
LineReflowStatus* aLineReflowStatus)
{
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=",
static_cast<void*>(aLine.get()), pushCount);
if (aFrame) {
nsFrame::ListTag(stdout, aFrame);
}
else {
printf("(null)");
}
printf("\n");
if (gReallyNoisyReflow) {
aLine->List(stdout, gNoiseIndent+1);
}
}
#endif
if (0 != pushCount) {
NS_ABORT_IF_FALSE(aLine->GetChildCount() > pushCount, "bad push");
NS_ABORT_IF_FALSE(nsnull != aFrame, "whoops");
NS_ASSERTION(nsFrameList(aFrame).GetLength() >= pushCount,
"Not enough frames to push");
// 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;
}
mLines.after_insert(aLine, newLine);
aLine->SetChildCount(aLine->GetChildCount() - pushCount);
#ifdef DEBUG
if (gReallyNoisyReflow) {
newLine->List(stdout, gNoiseIndent+1);
}
#endif
// Let line layout know that some frames are no longer part of its
// state.
aLineLayout.SplitLineTo(aLine->GetChildCount());
// If floats have been placed whose placeholders have been pushed to the new
// line, we need to reflow the old line again. We don't want to look at the
// frames in the new line, because as a large paragraph is laid out the
// we'd get O(N^2) performance. So instead we just check that the last
// float and the last below-current-line float are still in aLine.
if (!CheckPlaceholderInLine(this, aLine, GetLastFloat(aLine)) ||
!CheckPlaceholderInLine(this, aLine, aState.mBelowCurrentLineFloats.Tail())) {
*aLineReflowStatus = LINE_REFLOW_REDO_NO_PULL;
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
}
return NS_OK;
}
PRBool
nsBlockFrame::ShouldJustifyLine(nsBlockReflowState& aState,
line_iterator aLine)
{
while (++aLine != end_lines()) {
// There is another line
if (0 != aLine->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 !aLine->IsBlock();
}
// The next line is empty, try the next one
}
// XXX Not sure about this part
// Try our next-in-flows lines to answer the question
nsBlockFrame* nextInFlow = (nsBlockFrame*) GetNextInFlow();
while (nsnull != nextInFlow) {
for (line_iterator line = nextInFlow->begin_lines(),
line_end = nextInFlow->end_lines();
line != line_end;
++line)
{
if (0 != line->GetChildCount())
return !line->IsBlock();
}
nextInFlow = (nsBlockFrame*) nextInFlow->GetNextInFlow();
}
// This is the last line - so don't allow justification
return PR_FALSE;
}
PRBool
nsBlockFrame::PlaceLine(nsBlockReflowState& aState,
nsLineLayout& aLineLayout,
line_iterator aLine,
PRBool* aKeepReflowGoing)
{
// Trim extra white-space from the line before placing the frames
aLineLayout.TrimTrailingWhiteSpace();
// 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. It's 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 (mBullet && HaveOutsideBullet() && (aLine == mLines.front()) &&
(!aLineLayout.IsZeroHeight() || (aLine == mLines.back()))) {
nsHTMLReflowMetrics metrics;
ReflowBullet(aState, metrics);
aLineLayout.AddBulletFrame(mBullet, metrics);
addedBullet = PR_TRUE;
}
aLineLayout.VerticalAlignLine();
#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",
static_cast<void*>(aLine.get()),
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).
const nsStyleText* styleText = GetStyleText();
PRBool allowJustify = NS_STYLE_TEXT_ALIGN_JUSTIFY == styleText->mTextAlign &&
!aLineLayout.GetLineEndsInBR() &&
ShouldJustifyLine(aState, aLine);
aLineLayout.HorizontalAlignFrames(aLine->mBounds, allowJustify);
// XXX: not only bidi: right alignment can be broken after
// RelativePositionFrames!!!
// XXXldb Is something here considering relatively positioned frames at
// other than their original positions?
#ifdef IBMBIDI
// XXXldb Why don't we do this earlier?
if (aState.mPresContext->BidiEnabled()) {
if (!aState.mPresContext->IsVisualMode()) {
nsBidiPresUtils* bidiUtils = aState.mPresContext->GetBidiUtils();
if (bidiUtils && bidiUtils->IsSuccessful() ) {
bidiUtils->ReorderFrames(aLine->mFirstChild, aLine->GetChildCount());
} // bidiUtils
} // not visual mode
} // bidi enabled
#endif // IBMBIDI
nsRect combinedArea;
aLineLayout.RelativePositionFrames(combinedArea); // XXXldb This returned width as -15, 2001-06-12, Bugzilla
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->CachedIsEmpty()) {
// This line has some height. Therefore the application of the
// previous-bottom-margin should stick.
aState.mPrevBottomMargin.Zero();
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.
// We already called |ShouldApplyTopMargin|, and if we applied it
// then BRS_APPLYTOPMARGIN is set.
nscoord dy = aState.GetFlag(BRS_APPLYTOPMARGIN)
? -aState.mPrevBottomMargin.get() : 0;
newY = aState.mY + dy;
aLine->SlideBy(dy); // XXXldb Do we really want to do this?
}
// See if the line fit. If it doesn't we need to push it. Our first
// line will always fit.
if (mLines.front() != aLine &&
newY > aState.mBottomEdge &&
aState.mBottomEdge != NS_UNCONSTRAINEDSIZE) {
// Push this line and all of its children and anything else that
// follows to our next-in-flow
NS_ASSERTION((aState.mCurrentLine == aLine), "oops");
PushLines(aState, aLine.prev());
// Stop reflow and whack the reflow status if reflow hasn't
// already been stopped.
if (*aKeepReflowGoing) {
NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus);
*aKeepReflowGoing = PR_FALSE;
}
return PR_TRUE;
}
// May be needed below
PRBool wasAdjacentWIthTop = aState.IsAdjacentWithTop();
aState.mY = newY;
// Add the already placed current-line floats to the line
aLine->AppendFloats(aState.mCurrentLineFloats);
// Any below current line floats to place?
if (aState.mBelowCurrentLineFloats.NotEmpty()) {
// Reflow the below-current-line floats, then add them to the
// lines float list if there aren't any truncated floats.
if (aState.PlaceBelowCurrentLineFloats(aState.mBelowCurrentLineFloats,
wasAdjacentWIthTop)) {
aLine->AppendFloats(aState.mBelowCurrentLineFloats);
}
else {
// At least one float is truncated, so fix up any placeholders that got split and
// push the line. XXX It may be better to put the float on the next line, but this
// is not common enough to justify the complexity. Or maybe it is now...
nsIFrame* lastPlaceholder = aState.mOverflowPlaceholders.LastChild();
PushTruncatedPlaceholderLine(aState, aLine, lastPlaceholder, *aKeepReflowGoing);
}
}
// When a line has floats, factor them into the combined-area
// computations.
if (aLine->HasFloats()) {
// Combine the float combined area (stored in aState) and the
// value computed by the line layout code.
nsRect lineCombinedArea(aLine->GetCombinedArea());
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
printf(": lineCA=%d,%d,%d,%d floatCA=%d,%d,%d,%d\n",
lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height,
aState.mFloatCombinedArea.x, aState.mFloatCombinedArea.y,
aState.mFloatCombinedArea.width,
aState.mFloatCombinedArea.height);
#endif
lineCombinedArea.UnionRect(aState.mFloatCombinedArea, lineCombinedArea);
aLine->SetCombinedArea(lineCombinedArea);
#ifdef NOISY_COMBINED_AREA
printf(" ==> final lineCA=%d,%d,%d,%d\n",
lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height);
#endif
}
// Apply break-after clearing if necessary
// This must stay in sync with |ReflowDirtyLines|.
if (aLine->HasFloatBreakAfter()) {
aState.mY = aState.ClearFloats(aState.mY, aLine->GetBreakTypeAfter());
}
return PR_FALSE;
}
void
nsBlockFrame::PushLines(nsBlockReflowState& aState,
nsLineList::iterator aLineBefore)
{
nsLineList::iterator overBegin(aLineBefore.next());
// PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
PRBool firstLine = overBegin == begin_lines();
if (overBegin != end_lines()) {
// Remove floats in the lines from mFloats
nsFrameList floats;
nsIFrame* tail = nsnull;
CollectFloats(overBegin->mFirstChild, floats, &tail, PR_FALSE, PR_TRUE);
if (floats.NotEmpty()) {
// Push the floats onto the front of the overflow out-of-flows list
nsFrameList oofs = GetOverflowOutOfFlows();
if (oofs.NotEmpty()) {
floats.InsertFrames(nsnull, tail, oofs.FirstChild());
}
SetOverflowOutOfFlows(floats);
}
// overflow lines can already exist in some cases, in particular,
// when shrinkwrapping and we discover that the shrinkwap causes
// the height of some child block to grow which creates additional
// overflowing content. In such cases we must prepend the new
// overflow to the existing overflow.
nsLineList* overflowLines = RemoveOverflowLines();
if (!overflowLines) {
// XXXldb use presshell arena!
overflowLines = new nsLineList();
}
if (overflowLines) {
if (!overflowLines->empty()) {
mLines.back()->LastChild()->SetNextSibling(overflowLines->front()->mFirstChild);
}
overflowLines->splice(overflowLines->begin(), mLines, overBegin,
end_lines());
NS_ASSERTION(!overflowLines->empty(), "should not be empty");
// this takes ownership but it won't delete it immediately so we
// can keep using it.
SetOverflowLines(overflowLines);
// Mark all the overflow lines dirty so that they get reflowed when
// they are pulled up by our next-in-flow.
// XXXldb Can this get called O(N) times making the whole thing O(N^2)?
for (line_iterator line = overflowLines->begin(),
line_end = overflowLines->end();
line != line_end;
++line)
{
line->MarkDirty();
line->MarkPreviousMarginDirty();
line->mBounds.SetRect(0, 0, 0, 0);
if (line->HasFloats()) {
line->FreeFloats(aState.mFloatCacheFreeList);
}
}
}
}
// Break frame sibling list
if (!firstLine)
aLineBefore->LastChild()->SetNextSibling(nsnull);
#ifdef DEBUG
VerifyOverflowSituation();
#endif
}
/**
* Call this when a frame will be pulled from the block's
* next-in-flow into this frame. If it's a continuation placeholder,
* it should not be here so we push it into our overflow placeholders
* list. To avoid creating holes (e.g., the following block doesn't
* have a placeholder but the block after it does) we also need to
* pull all the following placeholders and put them in our overflow
* placeholders list too.
*
* If it's a first-in-flow placeholder, or it contains one, then we
* need to do this to the continuation placeholders.
*
* We return PR_TRUE if we removed the frame and it cannot be used.
* If we return PR_FALSE then the frame *must* be pulled immediately.
*/
PRBool
nsBlockFrame::HandleOverflowPlaceholdersForPulledFrame(
nsBlockReflowState& aState, nsIFrame* aFrame)
{
if (nsGkAtoms::placeholderFrame != aFrame->GetType()) {
// Descend into children that are not float containing blocks.
// We should encounter only first-in-flow placeholders, so the
// frame subtree rooted at aFrame should not change.
if (!aFrame->IsFloatContainingBlock()) {
for (nsIFrame* f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling()) {
#ifdef DEBUG
PRBool changed =
#endif
HandleOverflowPlaceholdersForPulledFrame(aState, f);
NS_ASSERTION(!changed, "Shouldn't find any continuation placeholders inside inlines");
}
}
return PR_FALSE;
}
PRBool taken = PR_TRUE;
nsIFrame* frame = aFrame;
if (!aFrame->GetPrevInFlow()) {
// First in flow frame. We only want to deal with its
// next in flows, if there are any.
taken = PR_FALSE;
frame = frame->GetNextInFlow();
if (!frame)
return PR_FALSE;
}
nsBlockFrame* parent = static_cast<nsBlockFrame*>(frame->GetParent());
// Remove aFrame and all its next in flows from their parents, but
// don't destroy the frames.
#ifdef DEBUG
nsresult rv =
#endif
parent->DoRemoveFrame(frame, PR_FALSE);
NS_ASSERTION(NS_SUCCEEDED(rv), "frame should be in parent's lists");
nsIFrame* lastOverflowPlace = aState.mOverflowPlaceholders.LastChild();
while (frame) {
NS_ASSERTION(IsContinuationPlaceholder(frame),
"Should only be dealing with continuation placeholders here");
parent = static_cast<nsBlockFrame*>(frame->GetParent());
ReparentFrame(frame, parent, this);
// continuation placeholders are always direct children of a block
nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(frame);
if (!parent->mFloats.RemoveFrame(outOfFlow)) {
nsAutoOOFFrameList oofs(parent);
#ifdef DEBUG
PRBool found =
#endif
oofs.mList.RemoveFrame(outOfFlow);
NS_ASSERTION(found, "Must have the out of flow in some child list");
}
ReparentFrame(outOfFlow, parent, this);
aState.mOverflowPlaceholders.InsertFrames(nsnull, lastOverflowPlace, frame);
// outOfFlow isn't inserted anywhere yet. Eventually the overflow
// placeholders get put into the overflow lines, and at the same time we
// insert the placeholders' out of flows into the overflow out-of-flows
// list.
lastOverflowPlace = frame;
frame = frame->GetNextInFlow();
}
return taken;
}
/**
* Call this when a line will be pulled from the block's
* next-in-flow's line.
*
* @return PR_TRUE we consumed the entire line, delete it and try again
*/
PRBool
nsBlockFrame::HandleOverflowPlaceholdersOnPulledLine(
nsBlockReflowState& aState, nsLineBox* aLine)
{
// First, see if it's a line of continuation placeholders. If it
// is, remove one and retry.
if (aLine->mFirstChild && IsContinuationPlaceholder(aLine->mFirstChild)) {
PRBool taken =
HandleOverflowPlaceholdersForPulledFrame(aState, aLine->mFirstChild);
NS_ASSERTION(taken, "We must have removed that frame");
return PR_TRUE;
}
// OK, it's a normal line. Scan it for floats with continuations that
// need to be taken care of. We won't need to change the line.
PRInt32 n = aLine->GetChildCount();
for (nsIFrame* f = aLine->mFirstChild; n > 0; f = f->GetNextSibling(), --n) {
#ifdef DEBUG
PRBool taken =
#endif
HandleOverflowPlaceholdersForPulledFrame(aState, f);
NS_ASSERTION(!taken, "Shouldn't be any continuation placeholders on this line");
}
return PR_FALSE;
}
// The overflowLines property is stored as a pointer to a line list,
// which must be deleted. However, the following functions all maintain
// the invariant that the property is never set if the list is empty.
PRBool
nsBlockFrame::DrainOverflowLines(nsBlockReflowState& aState)
{
#ifdef DEBUG
VerifyOverflowSituation();
#endif
nsLineList* overflowLines = nsnull;
nsLineList* ourOverflowLines = nsnull;
// First grab the prev-in-flows overflow lines
nsBlockFrame* prevBlock = (nsBlockFrame*) GetPrevInFlow();
if (prevBlock) {
overflowLines = prevBlock->RemoveOverflowLines();
if (overflowLines) {
NS_ASSERTION(! overflowLines->empty(),
"overflow lines should never be set and empty");
// Make all the frames on the overflow line list mine
nsIFrame* frame = overflowLines->front()->mFirstChild;
while (nsnull != frame) {
ReparentFrame(frame, prevBlock, this);
// Get the next frame
frame = frame->GetNextSibling();
}
// make the overflow out-of-flow frames mine too
nsAutoOOFFrameList oofs(prevBlock);
if (oofs.mList.NotEmpty()) {
for (nsIFrame* f = oofs.mList.FirstChild(); f; f = f->GetNextSibling()) {
ReparentFrame(f, prevBlock, this);
}
mFloats.InsertFrames(nsnull, nsnull, oofs.mList.FirstChild());
oofs.mList.SetFrames(nsnull);
}
}
// The lines on the overflow list have already been marked dirty and their
// previous margins marked dirty also.
}
// Don't need to reparent frames in our own overflow lines/oofs, because they're
// already ours. But we should put overflow floats back in mFloats.
ourOverflowLines = RemoveOverflowLines();
if (ourOverflowLines) {
nsAutoOOFFrameList oofs(this);
if (oofs.mList.NotEmpty()) {
// The overflow floats go after our regular floats
mFloats.AppendFrames(nsnull, oofs.mList.FirstChild());
oofs.mList.SetFrames(nsnull);
}
}
if (!overflowLines && !ourOverflowLines) {
// nothing to do; always the case for non-constrained-height reflows
return PR_FALSE;
}
NS_ASSERTION(aState.mOverflowPlaceholders.IsEmpty(),
"Should have no overflow placeholders yet");
// HANDLING CONTINUATION PLACEHOLDERS (floats only at the moment, because
// abs-pos frames don't have continuations)
//
// All continuation placeholders need to be moved to the front of
// our line list. We also need to maintain the invariant that at
// most one frame for a given piece of content is in our normal
// child list, by pushing all but the first placeholder to our
// overflow placeholders list.
//
// One problem we have to deal with is that some of these
// continuation placeholders may have been donated to us by a
// descendant block that was complete. We need to push them down to
// a lower block if possible.
//
// We keep the lists ordered so that prev in flows come before their
// next in flows. We do not worry about properly ordering the
// placeholders for different content relative to each other until
// the end. Then we sort them.
//
// When we're shuffling placeholders we also need to shuffle their out of
// flows to match. As we put placeholders into keepPlaceholders, we unhook
// their floats from mFloats. Later we put the floats back based on the
// order of the placeholders.
nsIFrame* lastOP = nsnull;
nsFrameList keepPlaceholders;
nsFrameList keepOutOfFlows;
nsIFrame* lastKP = nsnull;
nsIFrame* lastKOOF = nsnull;
nsLineList* lineLists[3] = { overflowLines, &mLines, ourOverflowLines };
static const PRPackedBool searchFirstLinesOnly[3] = { PR_FALSE, PR_TRUE, PR_FALSE };
for (PRInt32 i = 0; i < 3; ++i) {
nsLineList* ll = lineLists[i];
if (ll && !ll->empty()) {
line_iterator iter = ll->begin();
line_iterator iter_end = ll->end();
nsIFrame* lastFrame = nsnull;
while (iter != iter_end) {
PRUint32 n = iter->GetChildCount();
if (n == 0 || !IsContinuationPlaceholder(iter->mFirstChild)) {
if (lastFrame) {
lastFrame->SetNextSibling(iter->mFirstChild);
}
if (searchFirstLinesOnly[i]) {
break;
}
lastFrame = iter->LastChild();
++iter;
} else {
nsLineBox* line = iter;
iter = ll->erase(iter);
nsIFrame* next;
for (nsPlaceholderFrame* f = static_cast<nsPlaceholderFrame*>(line->mFirstChild);
n > 0; --n, f = static_cast<nsPlaceholderFrame*>(next)) {
if (!IsContinuationPlaceholder(f)) {
NS_ASSERTION(IsContinuationPlaceholder(f),
"Line frames should all be continuation placeholders");
}
next = f->GetNextSibling();
nsIFrame* fpif = f->GetPrevInFlow();
nsIFrame* oof = f->GetOutOfFlowFrame();
// Take this out of mFloats for now. We may put it back later in
// this function
#ifdef DEBUG
PRBool found =
#endif
mFloats.RemoveFrame(oof);
NS_ASSERTION(found, "Float should have been put in our mFloats list");
PRBool isAncestor = nsLayoutUtils::IsProperAncestorFrame(this, fpif);
if (isAncestor) {
// oops. we already have a prev-in-flow for this
// placeholder. We have to move this frame out of here. We
// can put it in our overflow placeholders.
aState.mOverflowPlaceholders.InsertFrame(nsnull, lastOP, f);
// Let oof dangle for now, because placeholders in
// mOverflowPlaceholders do not keep their floats in any child list
lastOP = f;
} else {
if (fpif->GetParent() == prevBlock) {
keepPlaceholders.InsertFrame(nsnull, lastKP, f);
keepOutOfFlows.InsertFrame(nsnull, lastKOOF, oof);
lastKP = f;
lastKOOF = oof;
} else {
// Ok, now we're in the tough situation where some child
// of prevBlock was complete and pushed its overflow
// placeholders up to prevBlock's overflow. We might be
// able to find a more appropriate parent for f somewhere
// down in our descendants.
NS_ASSERTION(nsLayoutUtils::IsProperAncestorFrame(prevBlock, fpif),
"bad prev-in-flow ancestor chain");
// Find the first ancestor of f's prev-in-flow that has a next in flow
// that can contain the float.
// That next in flow should become f's parent.
nsIFrame* fpAncestor;
for (fpAncestor = fpif->GetParent();
!fpAncestor->GetNextInFlow() || !fpAncestor->IsFloatContainingBlock();
fpAncestor = fpAncestor->GetParent())
;
if (fpAncestor == prevBlock) {
// We're still the best parent.
keepPlaceholders.InsertFrame(nsnull, lastKP, f);
keepOutOfFlows.InsertFrame(nsnull, lastKOOF, oof);
lastKP = f;
lastKOOF = oof;
} else {
// Just put it at the front of
// fpAncestor->GetNextInFlow()'s lines.
nsLineBox* newLine = aState.NewLineBox(f, 1, PR_FALSE);
if (newLine) {
nsBlockFrame* target =
static_cast<nsBlockFrame*>(fpAncestor->GetNextInFlow());
if (!target->mLines.empty()) {
f->SetNextSibling(target->mLines.front()->mFirstChild);
} else {
f->SetNextSibling(nsnull);
}
target->mLines.push_front(newLine);
ReparentFrame(f, this, target);
target->mFloats.InsertFrame(nsnull, nsnull, oof);
ReparentFrame(oof, this, target);
}
}
}
}
}
aState.FreeLineBox(line);
}
}
if (lastFrame) {
lastFrame->SetNextSibling(nsnull);
}
}
}
// Now join the line lists into mLines
if (overflowLines) {
if (!overflowLines->empty()) {
// Join the line lists
if (! mLines.empty())
{
// Remember to recompute the margins on the first line. This will
// also recompute the correct deltaY if necessary.
mLines.front()->MarkPreviousMarginDirty();
// Join the sibling lists together
nsIFrame* lastFrame = overflowLines->back()->LastChild();
lastFrame->SetNextSibling(mLines.front()->mFirstChild);
}
// Place overflow lines at the front of our line list
mLines.splice(mLines.begin(), *overflowLines);
NS_ASSERTION(overflowLines->empty(), "splice should empty list");
}
delete overflowLines;
}
if (ourOverflowLines) {
if (!ourOverflowLines->empty()) {
if (!mLines.empty()) {
mLines.back()->LastChild()->
SetNextSibling(ourOverflowLines->front()->mFirstChild);
}
// append the overflow to mLines
mLines.splice(mLines.end(), *ourOverflowLines);
}
delete ourOverflowLines;
}
// store the placeholders that we're keeping in our frame list
if (keepPlaceholders.NotEmpty()) {
keepPlaceholders.SortByContentOrder();
nsLineBox* newLine = aState.NewLineBox(keepPlaceholders.FirstChild(),
keepPlaceholders.GetLength(), PR_FALSE);
if (newLine) {
if (!mLines.empty()) {
keepPlaceholders.LastChild()->SetNextSibling(mLines.front()->mFirstChild);
}
mLines.push_front(newLine);
}
// Put the placeholders' out of flows into the float list
keepOutOfFlows.SortByContentOrder();
mFloats.InsertFrames(nsnull, nsnull, keepOutOfFlows.FirstChild());
}
return PR_TRUE;
}
nsLineList*
nsBlockFrame::GetOverflowLines() const
{
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES)) {
return nsnull;
}
nsLineList* lines = static_cast<nsLineList*>
(GetProperty(nsGkAtoms::overflowLinesProperty));
NS_ASSERTION(lines && !lines->empty(),
"value should always be stored and non-empty when state set");
return lines;
}
nsLineList*
nsBlockFrame::RemoveOverflowLines()
{
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES)) {
return nsnull;
}
nsLineList* lines = static_cast<nsLineList*>
(UnsetProperty(nsGkAtoms::overflowLinesProperty));
NS_ASSERTION(lines && !lines->empty(),
"value should always be stored and non-empty when state set");
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
return lines;
}
// Destructor function for the overflowLines frame property
static void
DestroyOverflowLines(void* aFrame,
nsIAtom* aPropertyName,
void* aPropertyValue,
void* aDtorData)
{
if (aPropertyValue) {
nsLineList* lines = static_cast<nsLineList*>(aPropertyValue);
nsPresContext *context = static_cast<nsPresContext*>(aDtorData);
nsLineBox::DeleteLineList(context, *lines);
delete lines;
}
}
// This takes ownership of aOverflowLines.
// XXX We should allocate overflowLines from presShell arena!
nsresult
nsBlockFrame::SetOverflowLines(nsLineList* aOverflowLines)
{
NS_ASSERTION(aOverflowLines, "null lines");
NS_ASSERTION(!aOverflowLines->empty(), "empty lines");
NS_ASSERTION(!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES),
"Overwriting existing overflow lines");
nsPresContext *presContext = PresContext();
nsresult rv = presContext->PropertyTable()->
SetProperty(this, nsGkAtoms::overflowLinesProperty, aOverflowLines,
DestroyOverflowLines, presContext);
// Verify that we didn't overwrite an existing overflow list
NS_ASSERTION(rv != NS_PROPTABLE_PROP_OVERWRITTEN, "existing overflow list");
AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
return rv;
}
nsFrameList
nsBlockFrame::GetOverflowOutOfFlows() const
{
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
return nsFrameList();
}
nsIFrame* result = static_cast<nsIFrame*>
(GetProperty(nsGkAtoms::overflowOutOfFlowsProperty));
NS_ASSERTION(result, "value should always be non-empty when state set");
return nsFrameList(result);
}
// This takes ownership of the frames
void
nsBlockFrame::SetOverflowOutOfFlows(const nsFrameList& aList)
{
if (aList.IsEmpty()) {
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
return;
}
nsIFrame* result = static_cast<nsIFrame*>
(UnsetProperty(nsGkAtoms::overflowOutOfFlowsProperty));
NS_ASSERTION(result, "value should always be non-empty when state set");
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
} else {
SetProperty(nsGkAtoms::overflowOutOfFlowsProperty,
aList.FirstChild(), nsnull);
AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
}
nsFrameList*
nsBlockFrame::GetOverflowPlaceholders() const
{
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_PLACEHOLDERS)) {
return nsnull;
}
nsFrameList* result = static_cast<nsFrameList*>
(GetProperty(nsGkAtoms::overflowPlaceholdersProperty));
NS_ASSERTION(result, "value should always be non-empty when state set");
return result;
}
//////////////////////////////////////////////////////////////////////
// Frame list manipulation routines
nsIFrame*
nsBlockFrame::LastChild()
{
if (! mLines.empty()) {
return mLines.back()->LastChild();
}
return nsnull;
}
NS_IMETHODIMP
nsBlockFrame::AppendFrames(nsIAtom* aListName,
nsIFrame* aFrameList)
{
if (nsnull == aFrameList) {
return NS_OK;
}
if (aListName) {
if (nsGkAtoms::absoluteList == aListName) {
return mAbsoluteContainer.AppendFrames(this, aListName, aFrameList);
}
else if (nsGkAtoms::floatList == aListName) {
mFloats.AppendFrames(nsnull, aFrameList);
return NS_OK;
}
else {
NS_ERROR("unexpected child list");
return NS_ERROR_INVALID_ARG;
}
}
// Find the proper last-child for where the append should go
nsIFrame* lastKid = nsnull;
nsLineBox* lastLine = mLines.empty() ? nsnull : mLines.back();
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(aFrameList, lastKid);
if (NS_SUCCEEDED(rv)) {
PresContext()->PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
}
return rv;
}
NS_IMETHODIMP
nsBlockFrame::InsertFrames(nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
"inserting after sibling frame with different parent");
if (aListName) {
if (nsGkAtoms::absoluteList == aListName) {
return mAbsoluteContainer.InsertFrames(this, aListName, aPrevFrame,
aFrameList);
}
else if (nsGkAtoms::floatList == aListName) {
mFloats.InsertFrames(this, aPrevFrame, aFrameList);
return NS_OK;
}
#ifdef IBMBIDI
else if (nsGkAtoms::nextBidi == aListName) {}
#endif // IBMBIDI
else {
NS_ERROR("unexpected child list");
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(aFrameList, aPrevFrame);
#ifdef IBMBIDI
if (aListName != nsGkAtoms::nextBidi)
#endif // IBMBIDI
if (NS_SUCCEEDED(rv)) {
PresContext()->PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
}
return rv;
}
static PRBool
ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame)
{
nsIAtom* type = aLastFrame->GetType();
if (type == nsGkAtoms::brFrame)
return PR_TRUE;
if (type == nsGkAtoms::textFrame)
return aLastFrame->HasTerminalNewline() &&
aLastFrame->GetStyleText()->WhiteSpaceIsSignificant();
if (type == nsGkAtoms::placeholderFrame)
return IsContinuationPlaceholder(aLastFrame);
return PR_FALSE;
}
nsresult
nsBlockFrame::AddFrames(nsIFrame* aFrameList,
nsIFrame* aPrevSibling)
{
// Clear our line cursor, since our lines may change.
ClearLineCursor();
if (nsnull == aFrameList) {
return NS_OK;
}
// If we're inserting at the beginning of our list and we have an
// inside bullet, insert after that bullet.
if (!aPrevSibling && mBullet && !HaveOutsideBullet()) {
NS_ASSERTION(!nsFrameList(aFrameList).ContainsFrame(mBullet),
"Trying to make mBullet prev sibling to itself");
aPrevSibling = mBullet;
}
nsIPresShell *presShell = PresContext()->PresShell();
// Attempt to find the line that contains the previous sibling
nsLineList::iterator prevSibLine = end_lines();
PRInt32 prevSiblingIndex = -1;
if (aPrevSibling) {
// XXX_perf This is technically O(N^2) in some cases, but by using
// RFind instead of Find, we make it O(N) in the most common case,
// which is appending content.
// Find the line that contains the previous sibling
if (! nsLineBox::RFindLineContaining(aPrevSibling,
begin_lines(), prevSibLine,
&prevSiblingIndex)) {
// Note: defensive code! RFindLineContaining must not return
// false in this case, so if it does...
NS_NOTREACHED("prev sibling not in line list");
aPrevSibling = nsnull;
prevSibLine = end_lines();
}
}
// Find the frame following aPrevSibling so that we can join up the
// two lists of frames.
nsIFrame* prevSiblingNextFrame = nsnull;
if (aPrevSibling) {
prevSiblingNextFrame = aPrevSibling->GetNextSibling();
// 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;
}
mLines.after_insert(prevSibLine, line);
prevSibLine->SetChildCount(prevSibLine->GetChildCount() - rem);
prevSibLine->MarkDirty();
}
// Now (partially) join the sibling lists together
aPrevSibling->SetNextSibling(aFrameList);
}
else if (! mLines.empty()) {
prevSiblingNextFrame = mLines.front()->mFirstChild;
}
// Walk through the new frames being added and update the line data
// structures to fit.
nsIFrame* newFrame = aFrameList;
while (newFrame) {
NS_ASSERTION(newFrame->GetType() != nsGkAtoms::placeholderFrame ||
(!newFrame->GetStyleDisplay()->IsAbsolutelyPositioned() &&
!newFrame->GetStyleDisplay()->IsFloating()),
"Placeholders should not float or be positioned");
PRBool isBlock = newFrame->GetStyleDisplay()->IsBlockOutside();
// If the frame is a block frame, or if there is no previous line or if the
// previous line is a block line we need to make a new line. We also make
// a new line, as an optimization, in the three cases we know we'll need it:
// if the previous line ended with a <br>, if it has significant whitespace and
// ended in a newline, or if it contains continuation placeholders.
if (isBlock || prevSibLine == end_lines() || prevSibLine->IsBlock() ||
(aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
// 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 != end_lines()) {
// Append new line after prevSibLine
mLines.after_insert(prevSibLine, line);
++prevSibLine;
}
else {
// New line is going before the other lines
mLines.push_front(line);
prevSibLine = begin_lines();
}
}
else {
prevSibLine->SetChildCount(prevSibLine->GetChildCount() + 1);
prevSibLine->MarkDirty();
}
aPrevSibling = newFrame;
newFrame = newFrame->GetNextSibling();
}
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;
}
nsBlockFrame::line_iterator
nsBlockFrame::RemoveFloat(nsIFrame* aFloat) {
// Find which line contains the float, so we can update
// the float cache.
line_iterator line = begin_lines(), line_end = end_lines();
for ( ; line != line_end; ++line) {
if (line->IsInline() && line->RemoveFloat(aFloat)) {
break;
}
}
// Unlink the placeholder *after* we searched the lines, because
// the line search uses the placeholder relationship.
nsFrameManager* fm = PresContext()->GetPresShell()->FrameManager();
nsPlaceholderFrame* placeholder = fm->GetPlaceholderFrameFor(aFloat);
if (placeholder) {
fm->UnregisterPlaceholderFrame(placeholder);
placeholder->SetOutOfFlowFrame(nsnull);
}
// Try to destroy if it's in mFloats.
if (mFloats.DestroyFrame(aFloat)) {
return line;
}
// Try our overflow list
{
nsAutoOOFFrameList oofs(this);
if (oofs.mList.DestroyFrame(aFloat)) {
return line_end;
}
}
// If this is during reflow, it could be the out-of-flow frame for a
// placeholder in our block reflow state's mOverflowPlaceholders. But that's
// OK; it's not part of any child list, so we can just go ahead and delete it.
aFloat->Destroy();
return line_end;
}
static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock)
{
nsLineList::iterator line = aBlock->begin_lines();
nsLineList::iterator endLine = aBlock->end_lines();
while (line != endLine) {
if (line->IsBlock()) {
nsIFrame* f = line->mFirstChild;
void* bf;
if (NS_SUCCEEDED(f->QueryInterface(kBlockFrameCID, &bf))) {
MarkAllDescendantLinesDirty(static_cast<nsBlockFrame*>(f));
}
}
line->MarkDirty();
++line;
}
}
static void MarkSameSpaceManagerLinesDirty(nsBlockFrame* aBlock)
{
nsBlockFrame* blockWithSpaceMgr = aBlock;
while (!(blockWithSpaceMgr->GetStateBits() & NS_BLOCK_SPACE_MGR)) {
void* bf;
if (NS_FAILED(blockWithSpaceMgr->GetParent()->
QueryInterface(kBlockFrameCID, &bf))) {
break;
}
blockWithSpaceMgr = static_cast<nsBlockFrame*>(blockWithSpaceMgr->GetParent());
}
// Mark every line at and below the line where the float was
// dirty, and mark their lines dirty too. We could probably do
// something more efficient --- e.g., just dirty the lines that intersect
// the float vertically.
MarkAllDescendantLinesDirty(blockWithSpaceMgr);
}
/**
* Returns PR_TRUE if aFrame is a block that has one or more float children.
*/
static PRBool BlockHasAnyFloats(nsIFrame* aFrame)
{
void* bf;
if (NS_FAILED(aFrame->QueryInterface(kBlockFrameCID, &bf)))
return PR_FALSE;
nsBlockFrame* block = static_cast<nsBlockFrame*>(aFrame);
if (block->GetFirstChild(nsGkAtoms::floatList))
return PR_TRUE;
nsLineList::iterator line = block->begin_lines();
nsLineList::iterator endLine = block->end_lines();
while (line != endLine) {
if (line->IsBlock() && BlockHasAnyFloats(line->mFirstChild))
return PR_TRUE;
++line;
}
return PR_FALSE;
}
NS_IMETHODIMP
nsBlockFrame::RemoveFrame(nsIAtom* aListName,
nsIFrame* aOldFrame)
{
nsresult rv = NS_OK;
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": remove ");
nsFrame::ListTag(stdout, aOldFrame);
printf("\n");
#endif
if (nsnull == aListName) {
PRBool hasFloats = BlockHasAnyFloats(aOldFrame);
rv = DoRemoveFrame(aOldFrame, PR_TRUE, PR_FALSE);
if (hasFloats) {
MarkSameSpaceManagerLinesDirty(this);
}
}
else if (nsGkAtoms::absoluteList == aListName) {
return mAbsoluteContainer.RemoveFrame(this, aListName, aOldFrame);
}
else if (nsGkAtoms::floatList == aListName) {
nsIFrame* curFrame = aOldFrame;
// Make sure to delete all the continuations for the float frame
// we are removing; this way is a bit messy, but so is the rest of the code.
// See bug 390762.
do {
nsIFrame* continuation = curFrame->GetNextContinuation();
nsBlockFrame* curParent = static_cast<nsBlockFrame*>(curFrame->GetParent());
curParent->RemoveFloat(curFrame);
MarkSameSpaceManagerLinesDirty(curParent);
curFrame = continuation;
} while (curFrame);
}
#ifdef IBMBIDI
else if (nsGkAtoms::nextBidi == aListName) {
// Skip the call to |FrameNeedsReflow| below by returning now.
return DoRemoveFrame(aOldFrame, PR_TRUE, PR_FALSE);
}
#endif // IBMBIDI
else {
NS_ERROR("unexpected child list");
rv = NS_ERROR_INVALID_ARG;
}
if (NS_SUCCEEDED(rv)) {
PresContext()->PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
}
return rv;
}
void
nsBlockFrame::DoRemoveOutOfFlowFrame(nsIFrame* aFrame)
{
// First remove aFrame's next in flow
nsIFrame* nextInFlow = aFrame->GetNextInFlow();
if (nextInFlow) {
nsBlockFrame::DoRemoveOutOfFlowFrame(nextInFlow);
}
// Now remove aFrame
const nsStyleDisplay* display = aFrame->GetStyleDisplay();
// The containing block is always the parent of aFrame.
nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
// Remove aFrame from the appropriate list.
if (display->IsAbsolutelyPositioned()) {
block->mAbsoluteContainer.RemoveFrame(block,
nsGkAtoms::absoluteList,
aFrame);
aFrame->Destroy();
}
else {
// This also destroys the frame.
block->RemoveFloat(aFrame);
}
}
/**
* This helps us iterate over the list of all normal + overflow lines
*/
void
nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
nsLineList::iterator* aEndIterator,
PRBool* aInOverflowLines) {
if (*aIterator == *aEndIterator) {
if (!*aInOverflowLines) {
*aInOverflowLines = PR_TRUE;
// Try the overflow lines
nsLineList* overflowLines = GetOverflowLines();
if (overflowLines) {
*aIterator = overflowLines->begin();
*aEndIterator = overflowLines->end();
}
}
}
}
nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
line_iterator& aLine, PRBool aInOverflow)
: mFrame(aFrame), mLine(aLine), mInOverflowLines(nsnull)
{
if (aInOverflow) {
mInOverflowLines = aFrame->GetOverflowLines();
NS_ASSERTION(mInOverflowLines, "How can we be in overflow if there isn't any?");
}
}
PRBool
nsBlockInFlowLineIterator::Next()
{
++mLine;
line_iterator end = mInOverflowLines ? mInOverflowLines->end() : mFrame->end_lines();
if (mLine != end)
return PR_TRUE;
PRBool currentlyInOverflowLines = mInOverflowLines != nsnull;
while (PR_TRUE) {
if (currentlyInOverflowLines) {
mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
if (!mFrame)
return PR_FALSE;
mInOverflowLines = nsnull;
mLine = mFrame->begin_lines();
if (mLine != mFrame->end_lines())
return PR_TRUE;
} else {
mInOverflowLines = mFrame->GetOverflowLines();
if (mInOverflowLines) {
mLine = mInOverflowLines->begin();
NS_ASSERTION(mLine != mInOverflowLines->end(), "empty overflow line list?");
return PR_TRUE;
}
}
currentlyInOverflowLines = !currentlyInOverflowLines;
}
}
PRBool
nsBlockInFlowLineIterator::Prev()
{
line_iterator begin = mInOverflowLines ? mInOverflowLines->begin() : mFrame->begin_lines();
if (mLine != begin) {
--mLine;
return PR_TRUE;
}
PRBool currentlyInOverflowLines = mInOverflowLines != nsnull;
while (PR_TRUE) {
if (currentlyInOverflowLines) {
mLine = mFrame->end_lines();
if (mLine != mFrame->begin_lines()) {
--mLine;
return PR_TRUE;
}
} else {
mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
if (!mFrame)
return PR_FALSE;
mInOverflowLines = mFrame->GetOverflowLines();
if (mInOverflowLines) {
mLine = mInOverflowLines->end();
NS_ASSERTION(mLine != mInOverflowLines->begin(), "empty overflow line list?");
--mLine;
return PR_TRUE;
}
}
currentlyInOverflowLines = !currentlyInOverflowLines;
}
}
static nsresult RemoveBlockChild(nsIFrame* aFrame, PRBool aDestroyFrames)
{
if (!aFrame)
return NS_OK;
nsBlockFrame* nextBlock = static_cast<nsBlockFrame*>(aFrame->GetParent());
NS_ASSERTION(nextBlock->GetType() == nsGkAtoms::blockFrame ||
nextBlock->GetType() == nsGkAtoms::areaFrame,
"Our child's continuation's parent is not a block?");
return nextBlock->DoRemoveFrame(aFrame, aDestroyFrames);
}
// This function removes aDeletedFrame and all its continuations. It
// is optimized for deleting a whole series of frames. The easy
// implementation would invoke itself recursively on
// aDeletedFrame->GetNextContinuation, then locate the line containing
// aDeletedFrame and remove aDeletedFrame from that line. But here we
// start by locating aDeletedFrame and then scanning from that point
// on looking for continuations.
nsresult
nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, PRBool aDestroyFrames,
PRBool aRemoveOnlyFluidContinuations)
{
// Clear our line cursor, since our lines may change.
ClearLineCursor();
if (aDeletedFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
NS_ASSERTION(aDestroyFrames, "We can't not destroy out of flows");
DoRemoveOutOfFlowFrame(aDeletedFrame);
return NS_OK;
}
nsPresContext* presContext = PresContext();
nsIPresShell* presShell = presContext->PresShell();
if (NS_FRAME_IS_OVERFLOW_CONTAINER & aDeletedFrame->GetStateBits()) {
nsContainerFrame::DeleteNextInFlowChild(presContext, aDeletedFrame);
return NS_OK;
}
PRBool isPlaceholder = nsGkAtoms::placeholderFrame == aDeletedFrame->GetType();
if (isPlaceholder) {
nsFrameList* overflowPlaceholders = GetOverflowPlaceholders();
if (overflowPlaceholders && overflowPlaceholders->RemoveFrame(aDeletedFrame)) {
nsIFrame* nif = aDeletedFrame->GetNextInFlow();
if (aDestroyFrames) {
aDeletedFrame->Destroy();
} else {
aDeletedFrame->SetNextSibling(nsnull);
}
return RemoveBlockChild(nif, aDestroyFrames);
}
}
// Find the line and the previous sibling that contains
// deletedFrame; we also find the pointer to the line.
nsLineList::iterator line = mLines.begin(),
line_end = mLines.end();
PRBool searchingOverflowList = PR_FALSE;
nsIFrame* prevSibling = nsnull;
// Make sure we look in the overflow lines even if the normal line
// list is empty
TryAllLines(&line, &line_end, &searchingOverflowList);
while (line != line_end) {
nsIFrame* frame = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
if (frame == aDeletedFrame) {
goto found_frame;
}
prevSibling = frame;
frame = frame->GetNextSibling();
}
++line;
TryAllLines(&line, &line_end, &searchingOverflowList);
}
found_frame:;
if (line == line_end) {
NS_ERROR("can't find deleted frame in lines");
return NS_ERROR_FAILURE;
}
if (prevSibling && !prevSibling->GetNextSibling()) {
// We must have found the first frame in the overflow line list. So
// there is no prevSibling
prevSibling = nsnull;
}
NS_ASSERTION(!prevSibling || prevSibling->GetNextSibling() == aDeletedFrame, "bad prevSibling");
while ((line != line_end) && (nsnull != aDeletedFrame)) {
NS_ASSERTION(this == aDeletedFrame->GetParent(), "messed up delete code");
NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line");
// If the frame being deleted is the last one on the line then
// optimize away the line->Contains(next-in-flow) call below.
PRBool isLastFrameOnLine = (1 == line->GetChildCount() ||
line->LastChild() == aDeletedFrame);
// Remove aDeletedFrame from the line
nsIFrame* nextFrame = aDeletedFrame->GetNextSibling();
if (line->mFirstChild == aDeletedFrame) {
// We should be setting this to null if aDeletedFrame
// is the only frame on the line. HOWEVER in that case
// we will be removing the line anyway, see below.
line->mFirstChild = nextFrame;
}
// Hmm, this won't do anything if we're removing a frame in the first
// overflow line... Hopefully doesn't matter
--line;
if (line != line_end && !line->IsBlock()) {
// Since we just removed a frame that follows some inline
// frames, we need to reflow the previous line.
line->MarkDirty();
}
++line;
// Take aDeletedFrame out of the sibling list. Note that
// prevSibling will only be nsnull when we are deleting the very
// first frame in the main or overflow list.
if (prevSibling) {
prevSibling->SetNextSibling(nextFrame);
}
// Update the child count of the line to be accurate
PRInt32 lineChildCount = line->GetChildCount();
lineChildCount--;
line->SetChildCount(lineChildCount);
// Destroy frame; capture its next continuation first in case we need
// to destroy that too.
nsIFrame* deletedNextContinuation = aRemoveOnlyFluidContinuations ?
aDeletedFrame->GetNextInFlow() : aDeletedFrame->GetNextContinuation();
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p frame=",
searchingOverflowList?"overflow":"normal", line.get());
nsFrame::ListTag(stdout, aDeletedFrame);
printf(" prevSibling=%p deletedNextContinuation=%p\n", prevSibling, deletedNextContinuation);
#endif
if (aDestroyFrames) {
aDeletedFrame->Destroy();
} else {
aDeletedFrame->SetNextSibling(nsnull);
}
aDeletedFrame = deletedNextContinuation;
PRBool haveAdvancedToNextLine = PR_FALSE;
// If line is empty, remove it now.
if (0 == lineChildCount) {
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
searchingOverflowList?"overflow":"normal", line.get());
#endif
nsLineBox *cur = line;
if (!searchingOverflowList) {
line = mLines.erase(line);
// 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(cur->GetCombinedArea());
#ifdef NOISY_BLOCK_INVALIDATE
printf("%p invalidate 10 (%d, %d, %d, %d)\n",
this, lineCombinedArea.x, lineCombinedArea.y,
lineCombinedArea.width, lineCombinedArea.height);
#endif
Invalidate(lineCombinedArea);
} else {
nsLineList* lineList = RemoveOverflowLines();
line = lineList->erase(line);
if (!lineList->empty()) {
SetOverflowLines(lineList);
}
}
cur->Destroy(presShell);
// If we're removing a line, ReflowDirtyLines isn't going to
// know that it needs to slide lines unless something is marked
// dirty. So mark the previous margin of the next line dirty if
// there is one.
if (line != line_end) {
line->MarkPreviousMarginDirty();
}
haveAdvancedToNextLine = PR_TRUE;
} else {
// Make the line that just lost a frame dirty, and advance to
// the next line.
if (!deletedNextContinuation || isLastFrameOnLine ||
!line->Contains(deletedNextContinuation)) {
line->MarkDirty();
++line;
haveAdvancedToNextLine = PR_TRUE;
}
}
if (deletedNextContinuation) {
// Continuations for placeholder frames don't always appear in
// consecutive lines. So for placeholders, just continue the slow easy way.
if (isPlaceholder) {
return RemoveBlockChild(deletedNextContinuation, aDestroyFrames);
}
// See if we should keep looking in the current flow's line list.
if (deletedNextContinuation->GetParent() != this) {
// The deceased frames continuation is not a child of the
// current block. So break out of the loop so that we advance
// to the next parent.
break;
}
// If we advanced to the next line then check if we should switch to the
// overflow line list.
if (haveAdvancedToNextLine) {
if (line != line_end && !searchingOverflowList &&
!line->Contains(deletedNextContinuation)) {
// We have advanced to the next *normal* line but the next-in-flow
// is not there - force a switch to the overflow line list.
line = line_end;
}
PRBool wasSearchingOverflowList = searchingOverflowList;
TryAllLines(&line, &line_end, &searchingOverflowList);
if (NS_UNLIKELY(searchingOverflowList && !wasSearchingOverflowList &&
prevSibling)) {
// We switched to the overflow line list and we have a prev sibling
// (in the main list), in this case we don't want to pick up any
// sibling list from the deceased frames (bug 344557).
prevSibling->SetNextSibling(nsnull);
prevSibling = nsnull;
}
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: now on %s line=%p prevSibling=%p\n",
searchingOverflowList?"overflow":"normal", line.get(),
prevSibling);
#endif
}
}
}
#ifdef DEBUG
VerifyLines(PR_TRUE);
#endif
// Advance to next flow block if the frame has more continuations
return RemoveBlockChild(aDeletedFrame, aDestroyFrames);
}
nsresult
nsBlockFrame::StealFrame(nsPresContext* aPresContext,
nsIFrame* aChild,
PRBool aForceNormal)
{
NS_PRECONDITION(aPresContext && aChild, "null pointer");
if ((aChild->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)
&& !aForceNormal)
return nsContainerFrame::StealFrame(aPresContext, aChild);
// Find the line and the previous sibling that contains
// aChild; we also find the pointer to the line.
nsLineList::iterator line = mLines.begin(),
line_end = mLines.end();
PRBool searchingOverflowList = PR_FALSE;
nsIFrame* prevSibling = nsnull;
// Make sure we look in the overflow lines even if the normal line
// list is empty
TryAllLines(&line, &line_end, &searchingOverflowList);
while (line != line_end) {
nsIFrame* frame = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
if (frame == aChild) {
// Disconnect from sibling list
if (prevSibling)
prevSibling->SetNextSibling(frame->GetNextSibling());
else
line->mFirstChild = frame->GetNextSibling();
frame->SetNextSibling(nsnull);
// Register removal with the line boxes
PRInt32 count = line->GetChildCount();
line->SetChildCount(--count);
if (count > 0) {
line->MarkDirty();
}
else {
// Remove the line box
nsLineBox* lineBox = line;
if (searchingOverflowList) {
// Erase line, but avoid making the overflow line list empty
nsLineList* lineList = RemoveOverflowLines();
lineList->erase(line);
if (!lineList->empty()) {
nsresult rv = SetOverflowLines(lineList);
NS_ENSURE_SUCCESS(rv, rv);
}
}
else {
mLines.erase(line);
}
lineBox->Destroy(aPresContext->PresShell());
if (line != line_end) {
// Line disappeared, so tell next line it may have to change position
line->MarkPreviousMarginDirty();
}
}
// Ok, we're done
return NS_OK;
}
prevSibling = frame;
frame = frame->GetNextSibling();
}
++line;
TryAllLines(&line, &line_end, &searchingOverflowList);
}
return NS_ERROR_UNEXPECTED;
}
void
nsBlockFrame::DeleteNextInFlowChild(nsPresContext* aPresContext,
nsIFrame* aNextInFlow)
{
nsIFrame* prevInFlow = aNextInFlow->GetPrevInFlow();
NS_PRECONDITION(prevInFlow, "bad next-in-flow");
if (NS_FRAME_IS_OVERFLOW_CONTAINER & aNextInFlow->GetStateBits()) {
nsContainerFrame::DeleteNextInFlowChild(aPresContext, aNextInFlow);
}
else {
DoRemoveFrame(aNextInFlow);
}
}
////////////////////////////////////////////////////////////////////////
// Float support
nsresult
nsBlockFrame::ReflowFloat(nsBlockReflowState& aState,
nsPlaceholderFrame* aPlaceholder,
nsMargin& aFloatMargin,
nsReflowStatus& aReflowStatus)
{
// Reflow the float.
nsIFrame* floatFrame = aPlaceholder->GetOutOfFlowFrame();
aReflowStatus = NS_FRAME_COMPLETE;
#ifdef NOISY_FLOAT
printf("Reflow Float %p in parent %p, availSpace(%d,%d,%d,%d)\n",
aPlaceholder->GetOutOfFlowFrame(), this,
aState.mAvailSpaceRect.x, aState.mAvailSpaceRect.y,
aState.mAvailSpaceRect.width, aState.mAvailSpaceRect.height
);
#endif
// Compute the available width. By default, assume the width of the
// containing block.
nscoord availWidth;
const nsStyleDisplay* floatDisplay = floatFrame->GetStyleDisplay();
if (NS_STYLE_DISPLAY_TABLE != floatDisplay->mDisplay ||
eCompatibility_NavQuirks != aState.mPresContext->CompatibilityMode() ) {
availWidth = aState.mContentArea.width;
}
else {
// This quirk matches the one in nsBlockReflowState::FlowAndPlaceFloat
// give tables only the available space
// if they can shrink we may not be constrained to place
// them in the next line
availWidth = aState.mAvailSpaceRect.width;
// round down to twips per pixel so that we fit
// needed when prev. float has procentage width
// (maybe is a table flaw that makes table chose to round up
// but I don't want to change that, too risky)
nscoord twp = nsPresContext::CSSPixelsToAppUnits(1);
availWidth -= availWidth % twp;
}
// aState.mY is relative to the border-top, make it relative to the content-top
nscoord contentYOffset = aState.mY - aState.BorderPadding().top;
nscoord availHeight = NS_UNCONSTRAINEDSIZE == aState.mContentArea.height
? NS_UNCONSTRAINEDSIZE
: PR_MAX(0, aState.mContentArea.height - contentYOffset);
nsRect availSpace(aState.BorderPadding().left,
aState.BorderPadding().top,
availWidth, availHeight);
// construct the html reflow state for the float. ReflowBlock will
// initialize it.
nsHTMLReflowState floatRS(aState.mPresContext, aState.mReflowState,
floatFrame,
nsSize(availSpace.width, availSpace.height));
// Setup a block reflow state to reflow the float.
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState);
// Reflow the float
PRBool isAdjacentWithTop = aState.IsAdjacentWithTop();
nsIFrame* clearanceFrame = nsnull;
nsresult rv;
do {
nsCollapsingMargin margin;
PRBool mayNeedRetry = PR_FALSE;
floatRS.mDiscoveredClearance = nsnull;
// Only first in flow gets a top margin.
if (!floatFrame->GetPrevInFlow()) {
nsBlockReflowContext::ComputeCollapsedTopMargin(floatRS, &margin,
clearanceFrame, &mayNeedRetry);
if (mayNeedRetry && !clearanceFrame) {
floatRS.mDiscoveredClearance = &clearanceFrame;
// We don't need to push the space manager state because the the block has its own
// space manager that will be destroyed and recreated
}
}
nsMargin offsets; // Don't bother returning this to the caller; it's stored
// on a frame property anyawy
rv = brc.ReflowBlock(availSpace, PR_TRUE, margin,
0, isAdjacentWithTop,
offsets, nsnull, floatRS,
aReflowStatus);
} while (NS_SUCCEEDED(rv) && clearanceFrame);
// An incomplete reflow status means we should split the float
// if the height is constrained (bug 145305).
if (NS_FRAME_IS_NOT_COMPLETE(aReflowStatus) && (NS_UNCONSTRAINEDSIZE == availHeight))
aReflowStatus = NS_FRAME_COMPLETE;
//XXXfr Floats can't be overflow incomplete yet
if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aReflowStatus))
NS_FRAME_SET_INCOMPLETE(aReflowStatus);
if (NS_FRAME_IS_COMPLETE(aReflowStatus)) {
// Float is now complete, so delete the placeholder's next in
// flows, if any; their floats (which are this float's continuations)
// have already been deleted.
// XXX won't this be done later in nsLineLayout::ReflowFrame anyway??
nsIFrame* nextInFlow = aPlaceholder->GetNextInFlow();
if (nextInFlow) {
static_cast<nsHTMLContainerFrame*>(nextInFlow->GetParent())
->DeleteNextInFlowChild(aState.mPresContext, nextInFlow);
// that takes care of all subsequent nextinflows too
}
}
if (aReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) {
aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW;
}
if (floatFrame->GetType() == nsGkAtoms::letterFrame) {
// We never split floating first letters; an incomplete state for
// such frames simply means that there is more content to be
// reflowed on the line.
if (NS_FRAME_IS_NOT_COMPLETE(aReflowStatus))
aReflowStatus = NS_FRAME_COMPLETE;
}
if (NS_FAILED(rv)) {
return rv;
}
// Capture the margin information for the caller
const nsMargin& m = floatRS.mComputedMargin;
aFloatMargin.top = brc.GetTopMargin();
aFloatMargin.right = m.right;
// Only last in flows get a bottom margin
if (NS_FRAME_IS_COMPLETE(aReflowStatus)) {
brc.GetCarriedOutBottomMargin().Include(m.bottom);
}
aFloatMargin.bottom = brc.GetCarriedOutBottomMargin().get();
aFloatMargin.left = m.left;
const nsHTMLReflowMetrics& metrics = brc.GetMetrics();
// Set the rect, make sure the view is properly sized and positioned,
// and tell the frame we're done reflowing it
// XXXldb This seems like the wrong place to be doing this -- shouldn't
// we be doing this in nsBlockReflowState::FlowAndPlaceFloat after
// we've positioned the float, and shouldn't we be doing the equivalent
// of |::PlaceFrameView| here?
floatFrame->SetSize(nsSize(metrics.width, metrics.height));
if (floatFrame->HasView()) {
nsContainerFrame::SyncFrameViewAfterReflow(aState.mPresContext, floatFrame,
floatFrame->GetView(),
&metrics.mOverflowArea,
NS_FRAME_NO_MOVE_VIEW);
}
// Pass floatRS so the frame hierarchy can be used (redoFloatRS has the same hierarchy)
floatFrame->DidReflow(aState.mPresContext, &floatRS,
NS_FRAME_REFLOW_FINISHED);
#ifdef NOISY_FLOAT
printf("end ReflowFloat %p, sized to %d,%d\n",
floatFrame, metrics.width, metrics.height);
#endif
// If the placeholder was continued and its first-in-flow was followed by a
// <BR>, then cache the <BR>'s break type in aState.mFloatBreakType so that
// the next frame after the placeholder can combine that break type with its own
nsIFrame* prevPlaceholder = aPlaceholder->GetPrevInFlow();
if (prevPlaceholder) {
// the break occurs only after the last continued placeholder
PRBool lastPlaceholder = PR_TRUE;
nsIFrame* next = aPlaceholder->GetNextSibling();
if (next) {
if (nsGkAtoms::placeholderFrame == next->GetType()) {
lastPlaceholder = PR_FALSE;
}
}
if (lastPlaceholder) {
// get the containing block of prevPlaceholder which is our prev-in-flow
if (GetPrevInFlow()) {
// get the break type of the last line in mPrevInFlow
nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
line_iterator endLine = prevBlock->end_lines();
if (endLine != prevBlock->begin_lines()) {
--endLine;
if (endLine->HasFloatBreakAfter())
aState.mFloatBreakType = endLine->GetBreakTypeAfter();
}
}
else NS_ASSERTION(PR_FALSE, "no prev in flow");
}
}
return NS_OK;
}
//////////////////////////////////////////////////////////////////////
// Painting, event handling
PRIntn
nsBlockFrame::GetSkipSides() const
{
if (mState & NS_FRAME_IS_OVERFLOW_CONTAINER)
return (1 << NS_SIDE_TOP) | (1 << NS_SIDE_BOTTOM);
PRIntn skip = 0;
if (GetPrevInFlow()) {
skip |= 1 << NS_SIDE_TOP;
}
nsIFrame* nif = GetNextInFlow();
if (nif && !(nif->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
skip |= 1 << NS_SIDE_BOTTOM;
}
return skip;
}
#ifdef DEBUG
static void ComputeCombinedArea(nsLineList& aLines,
nscoord aWidth, nscoord aHeight,
nsRect& aResult)
{
nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
line != line_end;
++line) {
// Compute min and max x/y values for the reflowed frame's
// combined areas
nsRect lineCombinedArea(line->GetCombinedArea());
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;
}
}
aResult.x = xa;
aResult.y = ya;
aResult.width = xb - xa;
aResult.height = yb - ya;
}
#endif
PRBool
nsBlockFrame::IsVisibleInSelection(nsISelection* aSelection)
{
nsCOMPtr<nsIDOMHTMLHtmlElement> html(do_QueryInterface(mContent));
nsCOMPtr<nsIDOMHTMLBodyElement> body(do_QueryInterface(mContent));
if (html || body)
return PR_TRUE;
nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
PRBool visible;
nsresult rv = aSelection->ContainsNode(node, PR_TRUE, &visible);
return NS_SUCCEEDED(rv) && visible;
}
/* virtual */ void
nsBlockFrame::PaintTextDecorationLine(nsIRenderingContext& aRenderingContext,
nsPoint aPt,
nsLineBox* aLine,
nscolor aColor,
nscoord aOffset,
nscoord aAscent,
nscoord aSize,
const PRUint8 aDecoration)
{
NS_ASSERTION(!aLine->IsBlock(), "Why did we ask for decorations on a block?");
nscoord start = aLine->mBounds.x;
nscoord width = aLine->mBounds.width;
if (aLine == begin_lines().get()) {
// Adjust for the text-indent. See similar code in
// nsLineLayout::BeginLineReflow.
nscoord indent = 0;
const nsStyleText* styleText = GetStyleText();
nsStyleUnit unit = styleText->mTextIndent.GetUnit();
if (eStyleUnit_Coord == unit) {
indent = styleText->mTextIndent.GetCoordValue();
} else if (eStyleUnit_Percent == unit) {
// It's a percentage of the containing block width.
nsIFrame* containingBlock =
nsHTMLReflowState::GetContainingBlockFor(this);
NS_ASSERTION(containingBlock, "Must have containing block!");
indent = nscoord(styleText->mTextIndent.GetPercentValue() *
containingBlock->GetContentRect().width);
}
// Adjust the start position and the width of the decoration by the
// value of the indent. Note that indent can be negative; that's OK.
// It'll just increase the width (which can also happen to be
// negative!).
start += indent;
width -= indent;
}
// Only paint if we have a positive width
if (width > 0) {
const nsStyleVisibility* visibility = GetStyleVisibility();
PRBool isRTL = visibility->mDirection == NS_STYLE_DIRECTION_RTL;
nsRefPtr<gfxContext> ctx = (gfxContext*)
aRenderingContext.GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT);
PRInt32 app = PresContext()->AppUnitsPerDevPixel();
gfxPoint pt(PresContext()->AppUnitsToGfxUnits(start + aPt.x),
PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y));
gfxSize size(PresContext()->AppUnitsToGfxUnits(width),
PresContext()->AppUnitsToGfxUnits(aSize));
nsCSSRendering::PaintDecorationLine(
ctx, aColor, pt, size,
PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()),
PresContext()->AppUnitsToGfxUnits(aOffset),
PresContext()->AppUnitsToGfxUnits(aSize),
aDecoration, NS_STYLE_BORDER_STYLE_SOLID, isRTL);
}
}
#ifdef DEBUG
static void DebugOutputDrawLine(PRInt32 aDepth, nsLineBox* aLine, PRBool aDrawn) {
if (nsBlockFrame::gNoisyDamageRepair) {
nsFrame::IndentBy(stdout, aDepth+1);
nsRect lineArea = aLine->GetCombinedArea();
printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
aDrawn ? "draw" : "skip",
static_cast<void*>(aLine),
aLine->mBounds.x, aLine->mBounds.y,
aLine->mBounds.width, aLine->mBounds.height,
lineArea.x, lineArea.y,
lineArea.width, lineArea.height);
}
}
#endif
static nsresult
DisplayLine(nsDisplayListBuilder* aBuilder, const nsRect& aLineArea,
const nsRect& aDirtyRect, nsBlockFrame::line_iterator& aLine,
PRInt32 aDepth, PRInt32& aDrawnLines, const nsDisplayListSet& aLists,
nsBlockFrame* aFrame) {
// 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.
PRBool intersect = aLineArea.Intersects(aDirtyRect);
#ifdef DEBUG
if (nsBlockFrame::gLamePaintMetrics) {
aDrawnLines++;
}
DebugOutputDrawLine(aDepth, aLine.get(), intersect);
#endif
// The line might contain a placeholder for a visible out-of-flow, in which
// case we need to descend into it. If there is such a placeholder, we will
// have NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO set.
if (!intersect &&
!(aFrame->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO))
return NS_OK;
nsresult rv;
nsDisplayList aboveTextDecorations;
PRBool lineInline = aLine->IsInline();
if (lineInline) {
// Display the text-decoration for the hypothetical anonymous inline box
// that wraps these inlines
rv = aFrame->DisplayTextDecorations(aBuilder, aLists.Content(),
&aboveTextDecorations, aLine);
NS_ENSURE_SUCCESS(rv, rv);
}
// Block-level child backgrounds go on the blockBorderBackgrounds list ...
// Inline-level child backgrounds go on the regular child content list.
nsDisplayListSet childLists(aLists,
lineInline ? aLists.Content() : aLists.BlockBorderBackgrounds());
nsIFrame* kid = aLine->mFirstChild;
PRInt32 n = aLine->GetChildCount();
while (--n >= 0) {
rv = aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, childLists,
lineInline ? nsIFrame::DISPLAY_CHILD_INLINE : 0);
NS_ENSURE_SUCCESS(rv, rv);
kid = kid->GetNextSibling();
}
aLists.Content()->AppendToTop(&aboveTextDecorations);
return NS_OK;
}
NS_IMETHODIMP
nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists)
{
PRInt32 drawnLines; // Will only be used if set (gLamePaintMetrics).
PRInt32 depth = 0;
#ifdef DEBUG
if (gNoisyDamageRepair) {
depth = GetDepth();
nsRect ca;
::ComputeCombinedArea(mLines, mRect.width, mRect.height, ca);
nsFrame::IndentBy(stdout, depth);
ListTag(stdout);
printf(": bounds=%d,%d,%d,%d dirty(absolute)=%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);
}
PRTime start = LL_ZERO; // Initialize these variables to silence the compiler.
if (gLamePaintMetrics) {
start = PR_Now();
drawnLines = 0;
}
#endif
DisplayBorderBackgroundOutline(aBuilder, aLists);
if (GetPrevInFlow()) {
DisplayOverflowContainers(aBuilder, aDirtyRect, aLists);
}
aBuilder->MarkFramesForDisplayList(this, mFloats.FirstChild(), aDirtyRect);
aBuilder->MarkFramesForDisplayList(this, mAbsoluteContainer.GetFirstChild(), aDirtyRect);
// Don't use the line cursor if we might have a descendant placeholder ...
// it might skip lines that contain placeholders but don't themselves
// intersect with the dirty area.
nsLineBox* cursor = GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO
? nsnull : GetFirstLineContaining(aDirtyRect.y);
line_iterator line_end = end_lines();
nsresult rv = NS_OK;
if (cursor) {
for (line_iterator line = mLines.begin(cursor);
line != line_end;
++line) {
nsRect lineArea = line->GetCombinedArea();
if (!lineArea.IsEmpty()) {
// Because we have a cursor, the combinedArea.ys are non-decreasing.
// Once we've passed aDirtyRect.YMost(), we can never see it again.
if (lineArea.y >= aDirtyRect.YMost()) {
break;
}
rv = DisplayLine(aBuilder, lineArea, aDirtyRect, line, depth, drawnLines,
aLists, this);
if (NS_FAILED(rv))
break;
}
}
} else {
PRBool nonDecreasingYs = PR_TRUE;
PRInt32 lineCount = 0;
nscoord lastY = PR_INT32_MIN;
nscoord lastYMost = PR_INT32_MIN;
for (line_iterator line = begin_lines();
line != line_end;
++line) {
nsRect lineArea = line->GetCombinedArea();
rv = DisplayLine(aBuilder, lineArea, aDirtyRect, line, depth, drawnLines,
aLists, this);
if (NS_FAILED(rv))
break;
if (!lineArea.IsEmpty()) {
if (lineArea.y < lastY
|| lineArea.YMost() < lastYMost) {
nonDecreasingYs = PR_FALSE;
}
lastY = lineArea.y;
lastYMost = lineArea.YMost();
}
lineCount++;
}
if (NS_SUCCEEDED(rv) && nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
SetupLineCursor();
}
}
if (NS_SUCCEEDED(rv) && (nsnull != mBullet) && HaveOutsideBullet()) {
// Display outside bullets manually
rv = BuildDisplayListForChild(aBuilder, mBullet, aDirtyRect, aLists);
}
#ifdef DEBUG
if (gLamePaintMetrics) {
PRTime end = PR_Now();
PRInt32 numLines = mLines.size();
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
return rv;
}
#ifdef ACCESSIBILITY
NS_IMETHODIMP nsBlockFrame::GetAccessible(nsIAccessible** aAccessible)
{
*aAccessible = nsnull;
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
// block frame may be for <hr>
if (mContent->Tag() == nsGkAtoms::hr) {
return accService->CreateHTMLHRAccessible(static_cast<nsIFrame*>(this), aAccessible);
}
nsPresContext *aPresContext = PresContext();
if (!mBullet || !aPresContext) {
if (!mContent || !mContent->GetParent()) {
// Don't create accessible objects for the root content node, they are redundant with
// the nsDocAccessible object created with the document node
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMHTMLDocument> htmlDoc =
do_QueryInterface(mContent->GetDocument());
if (htmlDoc) {
nsCOMPtr<nsIDOMHTMLElement> body;
htmlDoc->GetBody(getter_AddRefs(body));
if (SameCOMIdentity(body, mContent)) {
// Don't create accessible objects for the body, they are redundant with
// the nsDocAccessible object created with the document node
return NS_ERROR_FAILURE;
}
}
// Not a bullet, treat as normal HTML container
return accService->CreateHyperTextAccessible(static_cast<nsIFrame*>(this), aAccessible);
}
// Create special list bullet accessible
const nsStyleList* myList = GetStyleList();
nsAutoString bulletText;
if (myList->mListStyleImage || myList->mListStyleType == NS_STYLE_LIST_STYLE_DISC ||
myList->mListStyleType == NS_STYLE_LIST_STYLE_CIRCLE ||
myList->mListStyleType == NS_STYLE_LIST_STYLE_SQUARE) {
bulletText.Assign(PRUnichar(0x2022));; // Unicode bullet character
}
else if (myList->mListStyleType != NS_STYLE_LIST_STYLE_NONE) {
mBullet->GetListItemText(*myList, bulletText);
}
return accService->CreateHTMLLIAccessible(static_cast<nsIFrame*>(this),
static_cast<nsIFrame*>(mBullet),
bulletText,
aAccessible);
}
#endif
void nsBlockFrame::ClearLineCursor() {
if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) {
return;
}
UnsetProperty(nsGkAtoms::lineCursorProperty);
RemoveStateBits(NS_BLOCK_HAS_LINE_CURSOR);
}
void nsBlockFrame::SetupLineCursor() {
if (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR
|| mLines.empty()) {
return;
}
SetProperty(nsGkAtoms::lineCursorProperty,
mLines.front(), nsnull);
AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
}
nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) {
if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) {
return nsnull;
}
nsLineBox* property = static_cast<nsLineBox*>
(GetProperty(nsGkAtoms::lineCursorProperty));
line_iterator cursor = mLines.begin(property);
nsRect cursorArea = cursor->GetCombinedArea();
while ((cursorArea.IsEmpty() || cursorArea.YMost() > y)
&& cursor != mLines.front()) {
cursor = cursor.prev();
cursorArea = cursor->GetCombinedArea();
}
while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y)
&& cursor != mLines.back()) {
cursor = cursor.next();
cursorArea = cursor->GetCombinedArea();
}
if (cursor.get() != property) {
SetProperty(nsGkAtoms::lineCursorProperty,
cursor.get(), nsnull);
}
return cursor.get();
}
/* virtual */ void
nsBlockFrame::ChildIsDirty(nsIFrame* aChild)
{
// See if the child is absolutely positioned
if (aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW &&
aChild->GetStyleDisplay()->IsAbsolutelyPositioned()) {
// do nothing
} else if (aChild == mBullet && HaveOutsideBullet()) {
// The bullet lives in the first line, unless the first line has
// height 0 and there is a second line, in which case it lives
// in the second line.
line_iterator bulletLine = begin_lines();
if (bulletLine != end_lines() && bulletLine->mBounds.height == 0 &&
bulletLine != mLines.back()) {
bulletLine = bulletLine.next();
}
if (bulletLine != end_lines()) {
MarkLineDirty(bulletLine);
}
// otherwise we have an empty line list, and ReflowDirtyLines
// will handle reflowing the bullet.
} else {
// Mark the line containing the child frame dirty.
line_iterator fline = FindLineFor(aChild);
if (fline != end_lines())
MarkLineDirty(fline);
}
nsBlockFrameSuper::ChildIsDirty(aChild);
}
//////////////////////////////////////////////////////////////////////
// Start Debugging
#ifdef NS_DEBUG
static PRBool
InLineList(nsLineList& aLines, nsIFrame* aFrame)
{
for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
line != line_end;
++line) {
nsIFrame* frame = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
if (frame == aFrame) {
return PR_TRUE;
}
frame = frame->GetNextSibling();
}
}
return PR_FALSE;
}
static PRBool
InSiblingList(nsLineList& aLines, nsIFrame* aFrame)
{
if (! aLines.empty()) {
nsIFrame* frame = aLines.front()->mFirstChild;
while (frame) {
if (frame == aFrame) {
return PR_TRUE;
}
frame = frame->GetNextSibling();
}
}
return PR_FALSE;
}
NS_IMETHODIMP
nsBlockFrame::VerifyTree() const
{
// XXX rewrite this
return NS_OK;
}
#endif
// End Debugging
//////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
nsBlockFrame::Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow)
{
if (aPrevInFlow) {
// Copy over the block/area frame type flags
nsBlockFrame* blockFrame = (nsBlockFrame*)aPrevInFlow;
SetFlags(blockFrame->mState &
(NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET));
}
nsresult rv = nsBlockFrameSuper::Init(aContent, aParent, aPrevInFlow);
if (!aPrevInFlow)
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
return rv;
}
NS_IMETHODIMP
nsBlockFrame::SetInitialChildList(nsIAtom* aListName,
nsIFrame* aChildList)
{
nsresult rv = NS_OK;
if (nsGkAtoms::absoluteList == aListName) {
mAbsoluteContainer.SetInitialChildList(this, aListName, aChildList);
}
else if (nsGkAtoms::floatList == aListName) {
mFloats.SetFrames(aChildList);
}
else {
nsPresContext* presContext = PresContext();
#ifdef DEBUG
// The only times a block that is an anonymous box is allowed to have a
// first-letter frame are when it's the block inside a non-anonymous cell,
// the block inside a fieldset, a scrolled content block, or a column
// content block. Note that this means that blocks which are the anonymous
// block in {ib} splits do NOT get first-letter frames. Also, a block that
// has a previous continuation can't have a first letter frame.
nsIAtom *pseudo = GetStyleContext()->GetPseudoType();
PRBool haveFirstLetterStyle =
!GetPrevContinuation() &&
(!pseudo ||
(pseudo == nsCSSAnonBoxes::cellContent &&
mParent->GetStyleContext()->GetPseudoType() == nsnull) ||
pseudo == nsCSSAnonBoxes::fieldsetContent ||
pseudo == nsCSSAnonBoxes::scrolledContent ||
pseudo == nsCSSAnonBoxes::columnContent) &&
nsRefPtr<nsStyleContext>(GetFirstLetterStyle(presContext)) != nsnull;
NS_ASSERTION(haveFirstLetterStyle ==
((mState & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0),
"NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
#endif
rv = AddFrames(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 = GetStyleDisplay();
if ((nsnull == GetPrevInFlow()) &&
(NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) &&
(nsnull == mBullet)) {
// Resolve style for the bullet frame
const nsStyleList* styleList = GetStyleList();
nsIAtom *pseudoElement;
switch (styleList->mListStyleType) {
case NS_STYLE_LIST_STYLE_DISC:
case NS_STYLE_LIST_STYLE_CIRCLE:
case NS_STYLE_LIST_STYLE_SQUARE:
pseudoElement = nsCSSPseudoElements::mozListBullet;
break;
default:
pseudoElement = nsCSSPseudoElements::mozListNumber;
break;
}
nsIPresShell *shell = presContext->PresShell();
nsStyleContext* parentStyle =
CorrectStyleParentFrame(this, pseudoElement)->GetStyleContext();
nsRefPtr<nsStyleContext> kidSC = shell->StyleSet()->
ResolvePseudoStyleFor(mContent, pseudoElement, parentStyle);
// Create bullet frame
nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC);
if (nsnull == bullet) {
return NS_ERROR_OUT_OF_MEMORY;
}
bullet->Init(mContent, this, nsnull);
// If the list bullet frame should be positioned inside then add
// it to the flow now.
if (NS_STYLE_LIST_STYLE_POSITION_INSIDE ==
styleList->mListStylePosition) {
AddFrames(bullet, nsnull);
mState &= ~NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
else {
mState |= NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET;
}
mBullet = bullet;
}
}
return NS_OK;
}
// static
PRBool
nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame)
{
nsIContent* content = aFrame->GetContent();
if (!content || !content->IsNodeOfType(nsINode::eHTML))
return PR_FALSE;
nsIAtom *localName = content->NodeInfo()->NameAtom();
return localName == nsGkAtoms::ol ||
localName == nsGkAtoms::ul ||
localName == nsGkAtoms::dir ||
localName == nsGkAtoms::menu;
}
PRBool
nsBlockFrame::RenumberLists(nsPresContext* aPresContext)
{
if (!FrameStartsCounterScope(this)) {
// If this frame doesn't start a counter scope then we don't need
// to renumber child list items.
return PR_FALSE;
}
// Setup initial list ordinal value
// XXX Map html's start property to counter-reset style
PRInt32 ordinal = 1;
nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
if (hc) {
const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::start);
if (attr && attr->Type() == nsAttrValue::eInteger) {
ordinal = attr->GetIntegerValue();
}
}
// Get to first-in-flow
nsBlockFrame* block = (nsBlockFrame*) GetFirstInFlow();
return RenumberListsInBlock(aPresContext, block, &ordinal, 0);
}
PRBool
nsBlockFrame::RenumberListsInBlock(nsPresContext* aPresContext,
nsBlockFrame* aBlockFrame,
PRInt32* aOrdinal,
PRInt32 aDepth)
{
PRBool renumberedABullet = PR_FALSE;
while (nsnull != aBlockFrame) {
// Examine each line in the block
for (line_iterator line = aBlockFrame->begin_lines(),
line_end = aBlockFrame->end_lines();
line != line_end;
++line) {
nsIFrame* kid = line->mFirstChild;
PRInt32 n = line->GetChildCount();
while (--n >= 0) {
PRBool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal, aDepth);
if (kidRenumberedABullet) {
line->MarkDirty();
renumberedABullet = PR_TRUE;
}
kid = kid->GetNextSibling();
}
}
// Advance to the next continuation
aBlockFrame = static_cast<nsBlockFrame*>(aBlockFrame->GetNextInFlow());
}
return renumberedABullet;
}
PRBool
nsBlockFrame::RenumberListsFor(nsPresContext* aPresContext,
nsIFrame* aKid,
PRInt32* aOrdinal,
PRInt32 aDepth)
{
NS_PRECONDITION(aPresContext && aKid && aOrdinal, "null params are immoral!");
// add in a sanity check for absurdly deep frame trees. See bug 42138
if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth)
return PR_FALSE;
PRBool kidRenumberedABullet = PR_FALSE;
// if the frame is a placeholder, then get the out of flow frame
nsIFrame* kid = nsPlaceholderFrame::GetRealFrameFor(aKid);
// drill down through any wrappers to the real frame
kid = kid->GetContentInsertionFrame();
// If the frame is a list-item and the frame implements our
// block frame API then get its bullet and set the list item
// ordinal.
const nsStyleDisplay* display = kid->GetStyleDisplay();
if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) {
// Make certain that the frame is a block frame in case
// something foreign has crept in.
nsBlockFrame* listItem;
nsresult rv = kid->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;
// Invalidate the bullet content area since it may look different now
nsRect damageRect(nsPoint(0, 0), listItem->mBullet->GetSize());
listItem->mBullet->Invalidate(damageRect);
}
}
// 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-resetter.
PRBool meToo = RenumberListsInBlock(aPresContext, listItem, aOrdinal, aDepth + 1);
if (meToo) {
kidRenumberedABullet = PR_TRUE;
}
}
}
else if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) {
if (FrameStartsCounterScope(kid)) {
// 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 is a block frame then go ahead
// and recurse into it, as it might have child list-items.
nsBlockFrame* kidBlock;
nsresult rv = kid->QueryInterface(kBlockFrameCID, (void**) &kidBlock);
if (NS_SUCCEEDED(rv)) {
kidRenumberedABullet = RenumberListsInBlock(aPresContext, kidBlock, aOrdinal, aDepth + 1);
}
}
}
return kidRenumberedABullet;
}
void
nsBlockFrame::ReflowBullet(nsBlockReflowState& aState,
nsHTMLReflowMetrics& aMetrics)
{
const nsHTMLReflowState &rs = aState.mReflowState;
// Reflow the bullet now
nsSize availSize;
// Make up a width since it doesn't really matter (XXX).
availSize.width = rs.ComputedWidth();
availSize.height = NS_UNCONSTRAINEDSIZE;
// Get the reason right.
// XXXwaterson Should this look just like the logic in
// nsBlockReflowContext::ReflowBlock and nsLineLayout::ReflowFrame?
nsHTMLReflowState reflowState(aState.mPresContext, rs,
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 =
#ifdef IBMBIDI
(NS_STYLE_DIRECTION_RTL == GetStyleVisibility()->mDirection)
// According to the CSS2 spec, section 12.6.1, outside marker box
// is distanced from the associated principal box's border edge.
// |rs.availableWidth| reflects exactly a border edge: it includes
// border, padding, and content area, without margins.
? rs.ComputedWidth() + rs.mComputedBorderPadding.LeftRight() +
reflowState.mComputedMargin.left :
#endif
- 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(nsRect(x, y, aMetrics.width, aMetrics.height));
mBullet->DidReflow(aState.mPresContext, &aState.mReflowState, NS_FRAME_REFLOW_FINISHED);
}
// This is used to scan frames for any float placeholders, add their
// floats to the list represented by aHead and aTail, and remove the
// floats from whatever list they might be in. We don't search descendants
// that are float containing blocks. The floats must be children of 'this'.
void nsBlockFrame::CollectFloats(nsIFrame* aFrame, nsFrameList& aList, nsIFrame** aTail,
PRBool aFromOverflow, PRBool aCollectSiblings) {
while (aFrame) {
// Don't descend into float containing blocks.
if (!aFrame->IsFloatContainingBlock()) {
nsIFrame *outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame);
if (outOfFlowFrame) {
// Make sure that its parent is us. Otherwise we don't want
// to mess around with it because it belongs to someone
// else. I think this could happen if the overflow lines
// contain a block descendant which owns its own floats.
NS_ASSERTION(outOfFlowFrame->GetParent() == this,
"Out of flow frame doesn't have the expected parent");
if (aFromOverflow) {
nsAutoOOFFrameList oofs(this);
oofs.mList.RemoveFrame(outOfFlowFrame);
} else {
mFloats.RemoveFrame(outOfFlowFrame);
}
aList.InsertFrame(nsnull, *aTail, outOfFlowFrame);
*aTail = outOfFlowFrame;
}
CollectFloats(aFrame->GetFirstChild(nsnull), aList, aTail, aFromOverflow,
PR_TRUE);
}
if (!aCollectSiblings)
break;
aFrame = aFrame->GetNextSibling();
}
}
void
nsBlockFrame::CheckFloats(nsBlockReflowState& aState)
{
#ifdef DEBUG
// Check that the float list is what we would have built
nsAutoVoidArray lineFloats;
for (line_iterator line = begin_lines(), line_end = end_lines();
line != line_end; ++line) {
if (line->HasFloats()) {
nsFloatCache* fc = line->GetFirstFloat();
while (fc) {
nsIFrame* floatFrame = fc->mPlaceholder->GetOutOfFlowFrame();
lineFloats.AppendElement(floatFrame);
fc = fc->Next();
}
}
}
nsAutoVoidArray storedFloats;
PRBool equal = PR_TRUE;
PRInt32 i = 0;
for (nsIFrame* f = mFloats.FirstChild(); f; f = f->GetNextSibling()) {
storedFloats.AppendElement(f);
if (i < lineFloats.Count() && lineFloats.ElementAt(i) != f) {
equal = PR_FALSE;
}
++i;
}
if (!equal || lineFloats.Count() != storedFloats.Count()) {
NS_WARNING("nsBlockFrame::CheckFloats: Explicit float list is out of sync with float cache");
#if defined(DEBUG_roc)
nsIFrameDebug::RootFrameList(PresContext(), stdout, 0);
for (i = 0; i < lineFloats.Count(); ++i) {
printf("Line float: %p\n", lineFloats.ElementAt(i));
}
for (i = 0; i < storedFloats.Count(); ++i) {
printf("Stored float: %p\n", storedFloats.ElementAt(i));
}
#endif
}
#endif
nsFrameList oofs = GetOverflowOutOfFlows();
if (oofs.NotEmpty()) {
// Floats that were pushed should be removed from our space
// manager. Otherwise the space manager's YMost or XMost might
// be larger than necessary, causing this block to get an
// incorrect desired height (or width). Some of these floats
// may not actually have been added to the space manager because
// they weren't reflowed before being pushed; that's OK,
// RemoveRegions will ignore them. It is safe to do this here
// because we know from here on the space manager will only be
// used for its XMost and YMost, not to place new floats and
// lines.
aState.mSpaceManager->RemoveTrailingRegions(oofs.FirstChild());
}
}
/* static */
PRBool
nsBlockFrame::BlockIsMarginRoot(nsIFrame* aBlock)
{
NS_PRECONDITION(aBlock, "Must have a frame");
#ifdef DEBUG
nsBlockFrame* blockFrame;
aBlock->QueryInterface(kBlockFrameCID, (void**)&blockFrame);
NS_ASSERTION(blockFrame, "aBlock must be a block");
#endif
nsIFrame* parent = aBlock->GetParent();
return (aBlock->GetStateBits() & NS_BLOCK_MARGIN_ROOT) ||
(parent && !parent->IsFloatContainingBlock() &&
parent->GetType() != nsGkAtoms::columnSetFrame);
}
/* static */
PRBool
nsBlockFrame::BlockNeedsSpaceManager(nsIFrame* aBlock)
{
NS_PRECONDITION(aBlock, "Must have a frame");
#ifdef DEBUG
nsBlockFrame* blockFrame;
aBlock->QueryInterface(kBlockFrameCID, (void**)&blockFrame);
NS_ASSERTION(blockFrame, "aBlock must be a block");
#endif
nsIFrame* parent = aBlock->GetParent();
return (aBlock->GetStateBits() & NS_BLOCK_SPACE_MGR) ||
(parent && !parent->IsFloatContainingBlock());
}
#ifdef IBMBIDI
nsresult
nsBlockFrame::ResolveBidi()
{
NS_ASSERTION(!GetPrevInFlow(),
"ResolveBidi called on non-first continuation");
if (!(GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION))
return NS_OK;
RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
nsPresContext* presContext = PresContext();
if (!presContext->BidiEnabled()) {
return NS_OK;
}
nsBidiPresUtils* bidiUtils = presContext->GetBidiUtils();
if (!bidiUtils)
return NS_ERROR_NULL_POINTER;
for (nsBlockFrame* curFrame = this;
curFrame; curFrame = NS_STATIC_CAST(nsBlockFrame*,
curFrame->GetNextContinuation())) {
if (!curFrame->mLines.empty()) {
nsresult rv = bidiUtils->Resolve(curFrame,
curFrame->mLines.front()->mFirstChild,
IsVisualFormControl(presContext));
if (NS_FAILED(rv))
return rv;
}
}
return NS_OK;
}
PRBool
nsBlockFrame::IsVisualFormControl(nsPresContext* aPresContext)
{
// This check is only necessary on visual bidi pages, because most
// visual pages use logical order for form controls so that they will
// display correctly on native widgets in OSs with Bidi support.
// So bail out if the page is not visual, or if the pref is
// set to use visual order on forms in visual pages
if (!aPresContext->IsVisualMode()) {
return PR_FALSE;
}
PRUint32 options = aPresContext->GetBidi();
if (IBMBIDI_CONTROLSTEXTMODE_LOGICAL != GET_BIDI_OPTION_CONTROLSTEXTMODE(options)) {
return PR_FALSE;
}
nsIContent* content = GetContent();
for ( ; content; content = content->GetParent()) {
if (content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
return PR_TRUE;
}
}
return PR_FALSE;
}
#endif
#ifdef DEBUG
void
nsBlockFrame::VerifyLines(PRBool aFinalCheckOK)
{
if (!gVerifyLines) {
return;
}
if (mLines.empty()) {
return;
}
// Add up the counts on each line. Also validate that IsFirstLine is
// set properly.
PRInt32 count = 0;
PRBool seenBlock = PR_FALSE;
line_iterator line, line_end;
for (line = begin_lines(), line_end = end_lines();
line != line_end;
++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();
}
// Then count the frames
PRInt32 frameCount = 0;
nsIFrame* frame = mLines.front()->mFirstChild;
while (frame) {
frameCount++;
frame = frame->GetNextSibling();
}
NS_ASSERTION(count == frameCount, "bad line list");
// Next: test that each line has right number of frames on it
for (line = begin_lines(), line_end = end_lines();
line != line_end;
) {
count = line->GetChildCount();
frame = line->mFirstChild;
while (--count >= 0) {
frame = frame->GetNextSibling();
}
++line;
if ((line != line_end) && (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()
{
nsBlockFrame* flow = (nsBlockFrame*) GetFirstInFlow();
while (nsnull != flow) {
nsLineList* overflowLines = GetOverflowLines();
if (nsnull != overflowLines) {
NS_ASSERTION(! overflowLines->empty(), "should not be empty if present");
NS_ASSERTION(overflowLines->front()->mFirstChild, "bad overflow list");
}
flow = (nsBlockFrame*) flow->GetNextInFlow();
}
}
PRInt32
nsBlockFrame::GetDepth() const
{
PRInt32 depth = 0;
nsIFrame* parent = mParent;
while (parent) {
parent = parent->GetParent();
depth++;
}
return depth;
}
#endif