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

876 lines
33 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:cindent:ts=2:et:sw=2:
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* David Baron <dbaron@fas.harvard.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsBlockReflowContext.h"
#include "nsLineLayout.h"
#include "nsHTMLIIDs.h"
#include "nsISpaceManager.h"
#include "nsIFontMetrics.h"
#include "nsIPresContext.h"
#include "nsIContent.h"
#include "nsIStyleContext.h"
#include "nsIReflowCommand.h"
#include "nsHTMLContainerFrame.h"
#include "nsBlockFrame.h"
#include "nsIDOMHTMLTableCellElement.h"
#include "nsIDOMHTMLBodyElement.h"
#include "nsLayoutAtoms.h"
#include "nsCOMPtr.h"
#ifdef NS_DEBUG
#undef NOISY_MAX_ELEMENT_SIZE
#undef REALLY_NOISY_MAX_ELEMENT_SIZE
#undef NOISY_VERTICAL_MARGINS
#else
#undef NOISY_MAX_ELEMENT_SIZE
#undef REALLY_NOISY_MAX_ELEMENT_SIZE
#undef NOISY_VERTICAL_MARGINS
#endif
nsBlockReflowContext::nsBlockReflowContext(nsIPresContext* aPresContext,
const nsHTMLReflowState& aParentRS,
PRBool aComputeMaxElementSize,
PRBool aComputeMaximumWidth)
: mPresContext(aPresContext),
mOuterReflowState(aParentRS),
mMetrics(aComputeMaxElementSize ? &mMaxElementSize : nsnull),
mMaxElementSize(0, 0),
mIsTable(PR_FALSE),
mComputeMaximumWidth(aComputeMaximumWidth),
mBlockShouldInvalidateItself(PR_FALSE)
{
mStyleBorder = nsnull;
mStyleMargin = nsnull;
mStylePadding = nsnull;
if (mComputeMaximumWidth)
mMetrics.mFlags |= NS_REFLOW_CALC_MAX_WIDTH;
}
void
nsBlockReflowContext::ComputeCollapsedTopMargin(nsIPresContext* aPresContext,
nsHTMLReflowState& aRS,
/* inout */ nsCollapsingMargin& aMargin)
{
// Get aFrame's top margin
aMargin.Include(aRS.mComputedMargin.top);
#ifdef NOISY_VERTICAL_MARGINS
nsFrame::ListTag(stdout, aRS.frame);
printf(": %d => %d\n", aRS.mComputedMargin.top, aMargin.get());
#endif
// Calculate aFrame's generational top-margin from its child
// blocks. Note that if aFrame has a non-zero top-border or
// top-padding then this step is skipped because it will be a margin
// root. It is also skipped if the frame is a margin root for other
// reasons.
if (0 == aRS.mComputedBorderPadding.top) {
nsFrameState state;
aRS.frame->GetFrameState(&state);
if (!(state & NS_BLOCK_MARGIN_ROOT)) {
nsBlockFrame* bf;
if (NS_SUCCEEDED(aRS.frame->QueryInterface(kBlockFrameCID,
NS_REINTERPRET_CAST(void**, &bf)))) {
// Ask the block frame for the top block child that we should
// try to collapse the top margin with.
// XXX If the block is empty, we need to check its bottom margin
// and its sibling's top margin (etc.) too! See XXXldb comment about
// emptyness below in PlaceBlock.
nsIFrame* childFrame = bf->GetTopBlockChild();
if (nsnull != childFrame) {
// Here is where we recur. Now that we have determined that a
// generational collapse is required we need to compute the
// child blocks margin and so in so that we can look into
// it. For its margins to be computed we need to have a reflow
// state for it.
nsSize availSpace(aRS.mComputedWidth, aRS.mComputedHeight);
nsHTMLReflowState reflowState(aPresContext, aRS, childFrame,
availSpace);
ComputeCollapsedTopMargin(aPresContext, reflowState, aMargin);
}
}
}
}
#ifdef NOISY_VERTICAL_MARGINS
nsFrame::ListTag(stdout, aRS.frame);
printf(": => %d\n", aMargin.get());
#endif
}
struct nsBlockHorizontalAlign {
nscoord mXOffset; // left edge
nscoord mLeftMargin;
nscoord mRightMargin;
};
// Given the width of the block frame and a suggested x-offset calculate
// the actual x-offset taking into account horizontal alignment. Also returns
// the actual left and right margin
void
nsBlockReflowContext::AlignBlockHorizontally(nscoord aWidth,
nsBlockHorizontalAlign &aAlign)
{
// Initialize OUT parameters
aAlign.mLeftMargin = mMargin.left;
aAlign.mRightMargin = mMargin.right;
// Get style unit associated with the left and right margins
nsStyleUnit leftUnit = mStyleMargin->mMargin.GetLeftUnit();
if (eStyleUnit_Inherit == leftUnit) {
leftUnit = GetRealMarginLeftUnit();
}
nsStyleUnit rightUnit = mStyleMargin->mMargin.GetRightUnit();
if (eStyleUnit_Inherit == rightUnit) {
rightUnit = GetRealMarginRightUnit();
}
// Apply post-reflow horizontal alignment. When a block element
// doesn't use it all of the available width then we need to
// align it using the text-align property.
if (NS_UNCONSTRAINEDSIZE != mSpace.width) {
// It is possible that the object reflowed was given a
// constrained width and ended up picking a different width
// (e.g. a table width a set width that ended up larger
// because its contents required it). When this happens we
// need to recompute auto margins because the reflow state's
// computations are no longer valid.
if (aWidth != mComputedWidth) {
if (eStyleUnit_Auto == leftUnit) {
aAlign.mXOffset = 0;
aAlign.mLeftMargin = 0;
}
if (eStyleUnit_Auto == rightUnit) {
aAlign.mRightMargin = 0;
}
}
// Compute how much remaining space there is, and in special
// cases apply it (normally we should get zero here because of
// the logic in nsHTMLReflowState).
nscoord remainingSpace = mSpace.XMost() - (aAlign.mXOffset + aWidth +
aAlign.mRightMargin);
if (remainingSpace > 0) {
// The block/table frame didn't use all of the available
// space. Synthesize margins for its horizontal placement.
if (eStyleUnit_Auto == leftUnit) {
if (eStyleUnit_Auto == rightUnit) {
// When both margins are auto, we center the block
aAlign.mXOffset += remainingSpace / 2;
}
else {
// When the left margin is auto we right align the block
aAlign.mXOffset += remainingSpace;
}
}
else if (eStyleUnit_Auto != rightUnit) {
// The block/table doesn't have auto margins.
// For normal (non-table) blocks we don't get here because
// nsHTMLReflowState::CalculateBlockSideMargins handles this.
// (I think there may be an exception to that, though...)
// We use a special value of the text-align property for
// HTML alignment (the CENTER element and DIV ALIGN=...)
// since it acts on blocks and tables rather than just
// being a text-align.
// So, check the text-align value from the parent to see if
// it has one of these special values.
const nsStyleText* styleText = mOuterReflowState.mStyleText;
if (styleText->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_RIGHT) {
aAlign.mXOffset += remainingSpace;
} else if (styleText->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_CENTER) {
aAlign.mXOffset += remainingSpace / 2;
} else {
// If we don't have a special text-align value indicating
// HTML alignment, then use the CSS rules.
// When neither margin is auto then the block is said to
// be over constrained, Depending on the direction, choose
// which margin to treat as auto.
PRUint8 direction = mOuterReflowState.mStyleVisibility->mDirection;
if (NS_STYLE_DIRECTION_RTL == direction) {
// The left margin becomes auto
aAlign.mXOffset += remainingSpace;
}
//else {
// The right margin becomes auto which is a no-op
//}
}
}
}
}
}
nsresult
nsBlockReflowContext::ReflowBlock(nsIFrame* aFrame,
const nsRect& aSpace,
PRBool aApplyTopMargin,
nsCollapsingMargin& aPrevBottomMargin,
PRBool aIsAdjacentWithTop,
nsMargin& aComputedOffsets,
nsReflowStatus& aFrameReflowStatus)
{
nsresult rv = NS_OK;
mFrame = aFrame;
mSpace = aSpace;
// Get reflow reason set correctly. It's possible that a child was
// created and then it was decided that it could not be reflowed
// (for example, a block frame that isn't at the start of a
// line). In this case the reason will be wrong so we need to check
// the frame state.
nsReflowReason reason = eReflowReason_Resize;
nsFrameState state;
aFrame->GetFrameState(&state);
if (NS_FRAME_FIRST_REFLOW & state) {
reason = eReflowReason_Initial;
}
else if (mNextRCFrame == aFrame) {
reason = eReflowReason_Incremental;
// Make sure we only incrementally reflow once
mNextRCFrame = nsnull;
}
else if (mOuterReflowState.reason == eReflowReason_StyleChange) {
reason = eReflowReason_StyleChange;
}
else if (mOuterReflowState.reason == eReflowReason_Dirty) {
if (state & NS_FRAME_IS_DIRTY)
reason = eReflowReason_Dirty;
}
else {
if (mOuterReflowState.reason == eReflowReason_Incremental) {
// If the incremental reflow command is a StyleChanged reflow
// and it's target is the current block, then make sure we send
// StyleChange reflow reasons down to all the children so that
// they don't over-optimize their reflow.
nsIReflowCommand* rc = mOuterReflowState.reflowCommand;
if (rc) {
nsIReflowCommand::ReflowType type;
rc->GetType(type);
if (type == nsIReflowCommand::StyleChanged) {
nsIFrame* target;
rc->GetTarget(target);
if (target == mOuterReflowState.frame) {
reason = eReflowReason_StyleChange;
}
}
else if (type == nsIReflowCommand::ReflowDirty &&
(state & NS_FRAME_IS_DIRTY)) {
reason = eReflowReason_Dirty;
}
}
/*
if (eReflowReason_Resize == reason) {
// we're doing a resize reflow, even though our outer reflow state is incremental
// text (and possibly other objects) don't do incremental painting for resize reflows
// so, we have to handle the invalidation for repainting ourselves
mBlockShouldInvalidateItself = PR_TRUE;
}
*/
}
}
// Setup reflow state for reflowing the frame
// XXX subtract out vertical margin?
nsSize availSpace(aSpace.width, aSpace.height);
/* We build a different reflow context based on the width attribute of the block,
* if it's a floater.
* Auto-width floaters need to have their containing-block size set explicitly,
* factoring in other floaters that impact it.
* It's possible this should be quirks-only.
* All other blocks proceed normally.
*/
// XXXldb We should really fix this in nsHTMLReflowState::InitConstraints instead.
const nsStylePosition* position;
aFrame->GetStyleData(eStyleStruct_Position,
(const nsStyleStruct*&)position);
nsStyleUnit widthUnit = position->mWidth.GetUnit();
const nsStyleDisplay* display;
aFrame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&)display);
if ((eStyleUnit_Auto == widthUnit) &&
((NS_STYLE_FLOAT_LEFT == display->mFloats) ||
(NS_STYLE_FLOAT_RIGHT == display->mFloats))) {
// Construct the reflow state using the ctor that explicitly
// constrains the containing block's width and height to the
// available width and height.
nsHTMLReflowState autoReflowState(mPresContext, mOuterReflowState, aFrame,
availSpace, aSpace.width, aSpace.height);
autoReflowState.reason = reason;
rv = DoReflowBlock(autoReflowState, reason, aFrame, aSpace,
aApplyTopMargin, aPrevBottomMargin,
aIsAdjacentWithTop,
aComputedOffsets,
aFrameReflowStatus);
}
else {
// Construct the reflow state using the ctor that will use the
// containing block's computed width and height (or handle derive
// appropriate values for an absolutely positioned frame).
nsHTMLReflowState normalReflowState(mPresContext, mOuterReflowState, aFrame,
availSpace, reason);
rv = DoReflowBlock(normalReflowState, reason, aFrame, aSpace,
aApplyTopMargin, aPrevBottomMargin,
aIsAdjacentWithTop,
aComputedOffsets,
aFrameReflowStatus);
}
return rv;
}
static void
ComputeShrinkwrapMargins(const nsStyleMargin* aStyleMargin, nscoord aWidth, nsMargin& aMargin, nscoord& aXToUpdate) {
nscoord boxWidth = aWidth;
float leftPct = 0.0;
float rightPct = 0.0;
if (eStyleUnit_Percent == aStyleMargin->mMargin.GetLeftUnit()) {
nsStyleCoord leftCoord;
aStyleMargin->mMargin.GetLeft(leftCoord);
leftPct = leftCoord.GetPercentValue();
} else {
boxWidth += aMargin.left;
}
if (eStyleUnit_Percent == aStyleMargin->mMargin.GetRightUnit()) {
nsStyleCoord rightCoord;
aStyleMargin->mMargin.GetRight(rightCoord);
rightPct = rightCoord.GetPercentValue();
} else {
boxWidth += aMargin.right;
}
// The total shrink wrap width "sww" is calculated by the expression:
// sww = bw + (mp * sww)
// where "bw" is the box width (frame width plus margins that aren't percentage
// based) and "mp" are the total margin percentages (i.e., the left percentage
// value plus the right percentage value)
// Solving for "sww" gives us:
// sww = bw / (1 - mp)
// Note that this is only well defined for "mp" less than 100%
// XXXldb Um... percentage margins are based on the containing block width.
float marginPct = leftPct + rightPct;
if (marginPct >= 1.0) {
// Ignore the right percentage and just use the left percentage
// XXX Pay attention to direction property...
marginPct = leftPct;
rightPct = 0.0;
}
if ((marginPct > 0.0) && (marginPct < 1.0)) {
double shrinkWrapWidth = float(boxWidth) / (1.0 - marginPct);
if (eStyleUnit_Percent == aStyleMargin->mMargin.GetLeftUnit()) {
aMargin.left = NSToCoordFloor((float)(shrinkWrapWidth * leftPct));
aXToUpdate += aMargin.left;
}
if (eStyleUnit_Percent == aStyleMargin->mMargin.GetRightUnit()) {
aMargin.right = NSToCoordFloor((float)(shrinkWrapWidth * rightPct));
}
}
}
nsresult
nsBlockReflowContext::DoReflowBlock(nsHTMLReflowState &aReflowState,
nsReflowReason aReason,
nsIFrame* aFrame,
const nsRect& aSpace,
PRBool aApplyTopMargin,
nsCollapsingMargin& aPrevBottomMargin,
PRBool aIsAdjacentWithTop,
nsMargin& aComputedOffsets,
nsReflowStatus& aFrameReflowStatus)
{
nsresult rv = NS_OK;
nsFrameState state;
aFrame->GetFrameState(&state);
aComputedOffsets = aReflowState.mComputedOffsets;
aReflowState.mLineLayout = nsnull;
if (!aIsAdjacentWithTop) {
aReflowState.isTopOfPage = PR_FALSE; // make sure this is cleared
}
mIsTable = NS_STYLE_DISPLAY_TABLE == aReflowState.mStyleDisplay->mDisplay;
mComputedWidth = aReflowState.mComputedWidth;
if (aApplyTopMargin) {
// Compute the childs collapsed top margin (its margin collpased
// with its first childs top-margin -- recursively).
ComputeCollapsedTopMargin(mPresContext, aReflowState, aPrevBottomMargin);
#ifdef NOISY_VERTICAL_MARGINS
nsFrame::ListTag(stdout, mOuterReflowState.frame);
printf(": reflowing ");
nsFrame::ListTag(stdout, aFrame);
printf(" margin => %d\n", aPrevBottomMargin.get());
#endif
// Adjust the available height if its constrained so that the
// child frame doesn't think it can reflow into its margin area.
if (NS_UNCONSTRAINEDSIZE != aReflowState.availableHeight) {
aReflowState.availableHeight -= aPrevBottomMargin.get();
}
}
mTopMargin = aPrevBottomMargin.get();
// Compute x/y coordinate where reflow will begin. Use the rules
// from 10.3.3 to determine what to apply. At this point in the
// reflow auto left/right margins will have a zero value.
mMargin = aReflowState.mComputedMargin;
mStyleBorder = aReflowState.mStyleBorder;
mStyleMargin = aReflowState.mStyleMargin;
mStylePadding = aReflowState.mStylePadding;
nscoord x;
nscoord y = aSpace.y + mTopMargin;
// If it's a right floated element, then calculate the x-offset
// differently
if (NS_STYLE_FLOAT_RIGHT == aReflowState.mStyleDisplay->mFloats) {
nscoord frameWidth;
if (NS_UNCONSTRAINEDSIZE == aReflowState.mComputedWidth) {
nsSize frameSize;
// Use the current frame width
aFrame->GetSize(frameSize);
frameWidth = frameSize.width;
} else {
frameWidth = aReflowState.mComputedWidth +
aReflowState.mComputedBorderPadding.left +
aReflowState.mComputedBorderPadding.right;
}
// if this is an unconstrained width reflow, then just place the floater at the left margin
if (NS_UNCONSTRAINEDSIZE == aSpace.width)
x = aSpace.x;
else
x = aSpace.XMost() - mMargin.right - frameWidth;
} else {
x = aSpace.x + mMargin.left;
}
mX = x;
mY = y;
// If it's an auto-width table, then it doesn't behave like other blocks
// XXX why not for a floating table too?
if (mIsTable && !aReflowState.mStyleDisplay->IsFloating()) {
// If this isn't the table's initial reflow, then use its existing
// width to determine where it will be placed horizontally
if (aReflowState.reason != eReflowReason_Initial) {
nsBlockHorizontalAlign align;
nsSize size;
aFrame->GetSize(size);
align.mXOffset = x;
AlignBlockHorizontally(size.width, align);
// Don't reset "mX". because PlaceBlock() will recompute the
// x-offset and expects "mX" to be at the left margin edge
x = align.mXOffset;
}
}
// If the element is relatively positioned, then adjust x and y accordingly
if (NS_STYLE_POSITION_RELATIVE == aReflowState.mStyleDisplay->mPosition) {
x += aReflowState.mComputedOffsets.left;
y += aReflowState.mComputedOffsets.top;
}
// Let frame know that we are reflowing it
aFrame->WillReflow(mPresContext);
// Position it and its view (if it has one)
// Note: Use "x" and "y" and not "mX" and "mY" because they more accurately
// represents where we think the block will be placed
aFrame->MoveTo(mPresContext, x, y);
nsContainerFrame::PositionFrameView(mPresContext, aFrame);
#ifdef DEBUG
mMetrics.width = nscoord(0xdeadbeef);
mMetrics.height = nscoord(0xdeadbeef);
mMetrics.ascent = nscoord(0xdeadbeef);
mMetrics.descent = nscoord(0xdeadbeef);
if (nsnull != mMetrics.maxElementSize) {
mMetrics.maxElementSize->width = nscoord(0xdeadbeef);
mMetrics.maxElementSize->height = nscoord(0xdeadbeef);
}
#endif
// Adjust spacemanager coordinate system for the frame. The
// spacemanager coordinates are <b>inside</b> the callers
// border+padding, but the x/y coordinates are not (recall that
// frame coordinates are relative to the parents origin and that the
// parents border/padding is <b>inside</b> the parent
// frame. Therefore we have to subtract out the parents
// border+padding before translating.
nscoord tx = x - mOuterReflowState.mComputedBorderPadding.left;
nscoord ty = y - mOuterReflowState.mComputedBorderPadding.top;
mOuterReflowState.mSpaceManager->Translate(tx, ty);
// See if this is the child's initial reflow and we are supposed to
// compute our maximum width
if (mComputeMaximumWidth && (eReflowReason_Initial == aReason)) {
nscoord oldAvailableWidth = aReflowState.availableWidth;
nscoord oldComputedWidth = aReflowState.mComputedWidth;
aReflowState.availableWidth = NS_UNCONSTRAINEDSIZE;
aReflowState.mComputedWidth = NS_UNCONSTRAINEDSIZE;
rv = aFrame->Reflow(mPresContext, mMetrics, aReflowState,
aFrameReflowStatus);
// Update the reflow metrics with the maximum width
mMetrics.mMaximumWidth = mMetrics.width;
#ifdef NOISY_REFLOW
printf("*** nsBlockReflowContext::ReflowBlock block %p returning max width %d\n",
aFrame, mMetrics.mMaximumWidth);
#endif
// The second reflow is just as a resize reflow with the constrained
// width
aReflowState.availableWidth = oldAvailableWidth;
aReflowState.mComputedWidth = oldComputedWidth;
aReason = eReflowReason_Resize;
}
rv = aFrame->Reflow(mPresContext, mMetrics, aReflowState,
aFrameReflowStatus);
mOuterReflowState.mSpaceManager->Translate(-tx, -ty);
#ifdef DEBUG
if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
if (CRAZY_WIDTH(mMetrics.width) || CRAZY_HEIGHT(mMetrics.height)) {
printf("nsBlockReflowContext: ");
nsFrame::ListTag(stdout, aFrame);
printf(" metrics=%d,%d!\n", mMetrics.width, mMetrics.height);
}
if ((nsnull != mMetrics.maxElementSize) &&
((nscoord(0xdeadbeef) == mMetrics.maxElementSize->width) ||
(nscoord(0xdeadbeef) == mMetrics.maxElementSize->height))) {
printf("nsBlockReflowContext: ");
nsFrame::ListTag(stdout, aFrame);
printf(" didn't set max-element-size!\n");
mMetrics.maxElementSize->width = 0;
mMetrics.maxElementSize->height = 0;
}
#ifdef REALLY_NOISY_MAX_ELEMENT_SIZE
// Note: there are common reflow situations where this *correctly*
// occurs; so only enable this debug noise when you really need to
// analyze in detail.
if ((nsnull != mMetrics.maxElementSize) &&
((mMetrics.maxElementSize->width > mMetrics.width) ||
(mMetrics.maxElementSize->height > mMetrics.height))) {
printf("nsBlockReflowContext: ");
nsFrame::ListTag(stdout, aFrame);
printf(": WARNING: maxElementSize=%d,%d > metrics=%d,%d\n",
mMetrics.maxElementSize->width,
mMetrics.maxElementSize->height,
mMetrics.width, mMetrics.height);
}
#endif
if ((mMetrics.width == nscoord(0xdeadbeef)) ||
(mMetrics.height == nscoord(0xdeadbeef)) ||
(mMetrics.ascent == nscoord(0xdeadbeef)) ||
(mMetrics.descent == nscoord(0xdeadbeef))) {
printf("nsBlockReflowContext: ");
nsFrame::ListTag(stdout, aFrame);
printf(" didn't set whad %d,%d,%d,%d!\n",
mMetrics.width, mMetrics.height,
mMetrics.ascent, mMetrics.descent);
}
}
#endif
#ifdef NOISY_MAX_ELEMENT_SIZE
if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
if (nsnull != mMetrics.maxElementSize) {
printf(" ");
nsFrame::ListTag(stdout, aFrame);
printf(": maxElementSize=%d,%d wh=%d,%d\n",
mMetrics.maxElementSize->width,
mMetrics.maxElementSize->height,
mMetrics.width, mMetrics.height);
}
}
#endif
aFrame->GetFrameState(&state);
if (0 == (NS_FRAME_OUTSIDE_CHILDREN & state)) {
// Provide overflow area for child that doesn't have any
mMetrics.mOverflowArea.x = 0;
mMetrics.mOverflowArea.y = 0;
mMetrics.mOverflowArea.width = mMetrics.width;
mMetrics.mOverflowArea.height = mMetrics.height;
}
// Now that frame has been reflowed at least one time make sure that
// the NS_FRAME_FIRST_REFLOW bit is cleared so that never give it an
// initial reflow reason again.
if (eReflowReason_Initial == aReason) {
aFrame->SetFrameState(state & ~NS_FRAME_FIRST_REFLOW);
}
if (!NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) {
// If frame is complete and has a next-in-flow, we need to delete
// them now. Do not do this when a break-before is signaled because
// the frame is going to get reflowed again (and may end up wanting
// a next-in-flow where it ends up).
if (NS_FRAME_IS_COMPLETE(aFrameReflowStatus)) {
nsIFrame* kidNextInFlow;
aFrame->GetNextInFlow(&kidNextInFlow);
if (nsnull != kidNextInFlow) {
// Remove all of the childs next-in-flows. Make sure that we ask
// the right parent to do the removal (it's possible that the
// parent is not this because we are executing pullup code)
/* XXX promote DeleteChildsNextInFlow to nsIFrame to elminate this cast */
nsHTMLContainerFrame* parent;
aFrame->GetParent((nsIFrame**)&parent);
parent->DeleteChildsNextInFlow(mPresContext, aFrame);
}
}
}
// If the block is shrink wrapping its width, then see if we have percentage
// based margins. If so, we can calculate them now that we know the shrink
// wrap width
if (NS_SHRINKWRAPWIDTH == aReflowState.mComputedWidth) {
ComputeShrinkwrapMargins(aReflowState.mStyleMargin, mMetrics.width, mMargin, mX);
}
return rv;
}
/**
* Attempt to place the block frame within the available space. If
* it fits, apply horizontal positioning (CSS 10.3.3), collapse
* margins (CSS2 8.3.1). Also apply relative positioning.
*/
PRBool
nsBlockReflowContext::PlaceBlock(PRBool aForceFit,
const nsMargin& aComputedOffsets,
nsCollapsingMargin& aBottomMarginResult,
nsRect& aInFlowBounds,
nsRect& aCombinedRect)
{
// Compute collapsed bottom margin value
aBottomMarginResult = mMetrics.mCarriedOutBottomMargin;
aBottomMarginResult.Include(mMargin.bottom);
// See if the block will fit in the available space
PRBool fits = PR_TRUE;
nscoord x = mX;
nscoord y = mY;
// When deciding whether it's empty we also need to take into
// account the overflow area
// XXXldb What should really matter is whether there exist non-
// empty frames in the block (with appropriate whitespace munging).
// Consider the case where we clip off the overflow with
// 'overflow: hidden' (which doesn't currently affect mOverflowArea,
// but probably should.
if ((0 == mMetrics.height) && (0 == mMetrics.mOverflowArea.height))
{
// Collapse the bottom margin with the top margin that was already
// applied.
aBottomMarginResult.Include(mTopMargin);
#ifdef NOISY_VERTICAL_MARGINS
printf(" ");
nsFrame::ListTag(stdout, mOuterReflowState.frame);
printf(": ");
nsFrame::ListTag(stdout, mFrame);
printf(" -- collapsing top & bottom margin together; y=%d spaceY=%d\n",
y, mSpace.y);
#endif
y = mSpace.y;
// Empty blocks do not have anything special done to them and they
// always fit. Note: don't force the width to 0
nsRect r(x, y, mMetrics.width, 0);
// Now place the frame and complete the reflow process
nsContainerFrame::FinishReflowChild(mFrame, mPresContext, mMetrics, x, y, 0);
aInFlowBounds = r;
// Retain combined area information in case we contain a floater
// and nothing else.
aCombinedRect = mMetrics.mOverflowArea;
aCombinedRect.x += x;
aCombinedRect.y += y;
}
else {
// See if the frame fit. If its the first frame then it always
// fits.
if (aForceFit || (y + mMetrics.height <= mSpace.YMost()))
{
// Calculate the actual x-offset and left and right margin
nsBlockHorizontalAlign align;
align.mXOffset = x;
AlignBlockHorizontally(mMetrics.width, align);
x = align.mXOffset;
mMargin.left = align.mLeftMargin;
mMargin.right = align.mRightMargin;
// Update the in-flow bounds rectangle
aInFlowBounds.SetRect(x, y,
mMetrics.width,
mMetrics.height);
// Compute combined-rect in callers coordinate system. The value
// returned in the reflow metrics is relative to the child
// frame.
aCombinedRect.x = mMetrics.mOverflowArea.x + x;
aCombinedRect.y = mMetrics.mOverflowArea.y + y;
aCombinedRect.width = mMetrics.mOverflowArea.width;
aCombinedRect.height = mMetrics.mOverflowArea.height;
// Apply CSS relative positioning to update x,y coordinates
// Note that this must be done after changing aCombinedRect
// since relatively positioned elements should act as if they
// were at their original position.
const nsStyleDisplay* styleDisp;
mFrame->GetStyleData(eStyleStruct_Display,
(const nsStyleStruct*&)styleDisp);
if (NS_STYLE_POSITION_RELATIVE == styleDisp->mPosition) {
x += aComputedOffsets.left;
y += aComputedOffsets.top;
}
// Now place the frame and complete the reflow process
nsContainerFrame::FinishReflowChild(mFrame, mPresContext, mMetrics, x, y, 0);
// Adjust the max-element-size in the metrics to take into
// account the margins around the block element. Note that we
// use the collapsed top and bottom margin values.
if (nsnull != mMetrics.maxElementSize) {
nsSize* m = mMetrics.maxElementSize;
nsMargin maxElemMargin = mMargin;
if (NS_SHRINKWRAPWIDTH == mComputedWidth) {
nscoord dummyXOffset;
// Base the margins on the max-element size
ComputeShrinkwrapMargins(mStyleMargin, m->width, maxElemMargin, dummyXOffset);
}
// Do not allow auto margins to impact the max-element size
// since they are springy and don't really count!
if (eStyleUnit_Auto != mStyleMargin->mMargin.GetLeftUnit()) {
m->width += maxElemMargin.left;
}
if (eStyleUnit_Auto != mStyleMargin->mMargin.GetRightUnit()) {
m->width += maxElemMargin.right;
}
#if 0 // XXX_fix_me
// Margin height should affect the max-element height (since
// auto top/bottom margins are always zero)
// XXXldb Should it?
m->height += mTopMargin + mBottomMargin;
#endif
}
}
else {
// Send the DidReflow() notification, but don't bother placing
// the frame
mFrame->DidReflow(mPresContext, NS_FRAME_REFLOW_FINISHED);
fits = PR_FALSE;
}
}
return fits;
}
// If we have an inherited margin its possible that its auto all the
// way up to the top of the tree. If that is the case, we need to know
// it.
nsStyleUnit
nsBlockReflowContext::GetRealMarginLeftUnit()
{
nsStyleUnit unit = eStyleUnit_Inherit;
nsIStyleContext* sc;
mFrame->GetStyleContext(&sc);
while ((nsnull != sc) && (eStyleUnit_Inherit == unit)) {
// Get parent style context
nsIStyleContext* psc;
psc = sc->GetParent();
NS_RELEASE(sc);
sc = psc;
if (nsnull != sc) {
const nsStyleMargin* margin = (const nsStyleMargin*)
sc->GetStyleData(eStyleStruct_Margin);
unit = margin->mMargin.GetLeftUnit();
}
}
NS_IF_RELEASE(sc);
return unit;
}
// If we have an inherited margin its possible that its auto all the
// way up to the top of the tree. If that is the case, we need to know
// it.
nsStyleUnit
nsBlockReflowContext::GetRealMarginRightUnit()
{
nsStyleUnit unit = eStyleUnit_Inherit;
nsIStyleContext* sc;
mFrame->GetStyleContext(&sc);
while ((nsnull != sc) && (eStyleUnit_Inherit == unit)) {
// Get parent style context
nsIStyleContext* psc;
psc = sc->GetParent();
NS_RELEASE(sc);
sc = psc;
if (nsnull != sc) {
const nsStyleMargin* margin = (const nsStyleMargin*)
sc->GetStyleData(eStyleStruct_Margin);
unit = margin->mMargin.GetRightUnit();
}
}
NS_IF_RELEASE(sc);
return unit;
}