Bug 375304. Make nsHTMLScrollFrame handle situations where the child content height depends on our height. patch by Eli Friedman, r+sr=roc,dbaron

This commit is contained in:
2007-12-04 19:05:57 -08:00
parent 378c5fd09f
commit 2aaa6867c5
5 changed files with 106 additions and 122 deletions

View File

@@ -784,12 +784,8 @@ CalculateContainingBlockSizeForAbsolutes(const nsHTMLReflowState& aReflowState,
// 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
// size as the dimensions.
nsIFrame* frame = aReflowState.frame;
if (nsLayoutUtils::IsInitialContainingBlock(frame)) {
return nsSize(-1, -1);
}
nsSize cbSize(aFrameSize);
// Containing block is relative to the padding edge
@@ -825,13 +821,9 @@ CalculateContainingBlockSizeForAbsolutes(const nsHTMLReflowState& aReflowState,
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) {
if (!lastButOneRS->mFlags.mAssumingHScrollbar) {
scrollbars.top = scrollbars.bottom = 0;
//}
}
if (!lastButOneRS->mFlags.mAssumingVScrollbar) {
scrollbars.left = scrollbars.right = 0;
}

View File

@@ -227,9 +227,6 @@ nsHTMLScrollFrame::GetType() const
/**
HTML scrolling implementation
We rely on the fact that if height is 'auto', changing the height of
the element does not require reflowing the contents.
All other things being equal, we prefer layouts with fewer scrollbars showing.
*/
@@ -239,6 +236,10 @@ struct ScrollReflowState {
nsGfxScrollFrameInner::ScrollbarStyles mStyles;
nsMargin mComputedBorder;
// === Filled in by ReflowScrolledFrame ===
PRPackedBool mReflowedContentsWithHScrollbar;
PRPackedBool mReflowedContentsWithVScrollbar;
// === Filled in when TryLayout succeeds ===
// The area of the scrollport, in coordinates relative to the scrollframe
nsRect mScrollPortRect;
@@ -322,16 +323,29 @@ GetScrollbarMetrics(nsBoxLayoutState& aState, nsIBox* aBox, nsSize* aMin,
*/
PRBool
nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
const nsHTMLReflowMetrics& aKidMetrics,
PRBool aAssumeVScroll, PRBool aAssumeHScroll,
PRBool aForce)
nsHTMLReflowMetrics* aKidMetrics,
PRBool aAssumeHScroll, PRBool aAssumeVScroll,
PRBool aForce, nsresult* aResult)
{
*aResult = NS_OK;
if ((aState->mStyles.mVertical == NS_STYLE_OVERFLOW_HIDDEN && aAssumeVScroll) ||
(aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && aAssumeHScroll)) {
NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!");
return PR_FALSE;
}
if (aAssumeVScroll != aState->mReflowedContentsWithVScrollbar ||
((mInner.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) &&
aAssumeHScroll != aState->mReflowedContentsWithHScrollbar)) {
nsresult rv = ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll,
aKidMetrics, PR_FALSE);
if (NS_FAILED(rv)) {
*aResult = rv;
return PR_FALSE;
}
}
nsSize vScrollbarMinSize(0, 0);
nsSize vScrollbarPrefSize(0, 0);
if (mInner.mVScrollbarBox) {
@@ -356,9 +370,9 @@ nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
// XXXldb Can we depend more on ComputeSize here?
nsSize desiredInsideBorderSize;
desiredInsideBorderSize.width = vScrollbarDesiredWidth +
PR_MAX(aKidMetrics.width, hScrollbarDesiredWidth);
PR_MAX(aKidMetrics->width, hScrollbarDesiredWidth);
desiredInsideBorderSize.height = hScrollbarDesiredHeight +
PR_MAX(aKidMetrics.height, vScrollbarDesiredHeight);
PR_MAX(aKidMetrics->height, vScrollbarDesiredHeight);
aState->mInsideBorderSize =
ComputeInsideBorderSize(aState, desiredInsideBorderSize);
nsSize scrollPortSize = nsSize(PR_MAX(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
@@ -408,7 +422,7 @@ nsHTMLScrollFrame::TryLayout(ScrollReflowState* aState,
}
nsresult
nsHTMLScrollFrame::ReflowScrolledFrame(const ScrollReflowState& aState,
nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
PRBool aAssumeHScroll,
PRBool aAssumeVScroll,
nsHTMLReflowMetrics* aMetrics,
@@ -416,13 +430,26 @@ nsHTMLScrollFrame::ReflowScrolledFrame(const ScrollReflowState& aState,
{
// these could be NS_UNCONSTRAINEDSIZE ... PR_MIN arithmetic should
// be OK
nscoord paddingLR = aState.mReflowState.mComputedPadding.LeftRight();
nscoord paddingLR = aState->mReflowState.mComputedPadding.LeftRight();
nscoord availWidth = aState.mReflowState.ComputedWidth() + paddingLR;
nscoord availWidth = aState->mReflowState.ComputedWidth() + paddingLR;
nscoord computedHeight = aState->mReflowState.ComputedHeight();
nscoord computedMinHeight = aState->mReflowState.mComputedMinHeight;
nscoord computedMaxHeight = aState->mReflowState.mComputedMaxHeight;
if (aAssumeHScroll) {
nsSize hScrollbarPrefSize =
mInner.mHScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
if (computedHeight != NS_UNCONSTRAINEDSIZE)
computedHeight = PR_MAX(0, computedHeight - hScrollbarPrefSize.height);
computedMinHeight = PR_MAX(0, computedMinHeight - hScrollbarPrefSize.height);
if (computedMaxHeight != NS_UNCONSTRAINEDSIZE)
computedMaxHeight = PR_MAX(0, computedMaxHeight - hScrollbarPrefSize.height);
}
if (aAssumeVScroll) {
nsSize vScrollbarPrefSize =
mInner.mVScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState.mBoxState));
mInner.mVScrollbarBox->GetPrefSize(const_cast<nsBoxLayoutState&>(aState->mBoxState));
availWidth = PR_MAX(0, availWidth - vScrollbarPrefSize.width);
}
@@ -430,19 +457,22 @@ nsHTMLScrollFrame::ReflowScrolledFrame(const ScrollReflowState& aState,
// padding is.
mInner.mScrolledFrame->
SetProperty(nsGkAtoms::usedPaddingProperty,
new nsMargin(aState.mReflowState.mComputedPadding),
new nsMargin(aState->mReflowState.mComputedPadding),
nsCSSOffsetState::DestroyMarginFunc);
nsPresContext* presContext = PresContext();
// Pass PR_FALSE for aInit so we can pass in the correct padding
nsHTMLReflowState kidReflowState(presContext, aState.mReflowState,
nsHTMLReflowState kidReflowState(presContext, aState->mReflowState,
mInner.mScrolledFrame,
nsSize(availWidth, NS_UNCONSTRAINEDSIZE),
-1, -1, PR_FALSE);
kidReflowState.Init(presContext, -1, -1, nsnull,
&aState.mReflowState.mComputedPadding);
&aState->mReflowState.mComputedPadding);
kidReflowState.mFlags.mAssumingHScrollbar = aAssumeHScroll;
kidReflowState.mFlags.mAssumingVScrollbar = aAssumeVScroll;
kidReflowState.SetComputedHeight(computedHeight);
kidReflowState.mComputedMinHeight = computedMinHeight;
kidReflowState.mComputedMaxHeight = computedMaxHeight;
nsReflowStatus status;
nsresult rv = ReflowChild(mInner.mScrolledFrame, presContext, *aMetrics,
@@ -467,6 +497,9 @@ nsHTMLScrollFrame::ReflowScrolledFrame(const ScrollReflowState& aState,
aMetrics->mOverflowArea.UnionRect(aMetrics->mOverflowArea,
nsRect(0, 0, aMetrics->width, aMetrics->height));
aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
return rv;
}
@@ -526,13 +559,10 @@ nsresult
nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
const nsHTMLReflowMetrics& aDesiredSize)
{
PRBool currentlyUsingVScrollbar = GuessVScrollbarNeeded(*aState);
nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mFlags);
nsresult rv = ReflowScrolledFrame(*aState, PR_FALSE, currentlyUsingVScrollbar,
&kidDesiredSize, PR_TRUE);
if (NS_FAILED(rv))
return rv;
PRBool didUseScrollbar = currentlyUsingVScrollbar;
nsresult rv = ReflowScrolledFrame(aState, mInner.mHasHorizontalScrollbar,
GuessVScrollbarNeeded(*aState), &kidDesiredSize, PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
// There's an important special case ... if the child appears to fit
// in the inside-border rect (but overflows the scrollport), we
@@ -550,7 +580,10 @@ nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
// Detecting when we enter this special case is important for when
// people design layouts that exactly fit the container "most of the
// time".
if (currentlyUsingVScrollbar &&
// XXX Is this check really sufficient to catch all the incremental cases
// where the ideal case doesn't have a scrollbar?
if ((aState->mReflowedContentsWithHScrollbar || aState->mReflowedContentsWithVScrollbar) &&
aState->mStyles.mVertical != NS_STYLE_OVERFLOW_SCROLL &&
aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_SCROLL) {
nsSize insideBorderSize =
@@ -558,58 +591,45 @@ nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState,
nsSize(kidDesiredSize.width, kidDesiredSize.height));
nsRect scrolledRect = mInner.GetScrolledRect(insideBorderSize);
if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) {
// Let's pretend we had no vertical scrollbar coming in here
currentlyUsingVScrollbar = PR_FALSE;
rv = ReflowScrolledFrame(*aState, PR_FALSE, currentlyUsingVScrollbar,
// Let's pretend we had no scrollbars coming in here
rv = ReflowScrolledFrame(aState, PR_FALSE, PR_FALSE,
&kidDesiredSize, PR_FALSE);
if (NS_FAILED(rv))
return rv;
didUseScrollbar = PR_FALSE;
NS_ENSURE_SUCCESS(rv, rv);
}
}
// First try a layout without a horizontal scrollbar, then with.
if (TryLayout(aState, kidDesiredSize, didUseScrollbar, PR_FALSE, PR_FALSE))
// Try vertical scrollbar settings that leave the vertical scrollbar unchanged.
// Do this first because changing the vertical scrollbar setting is expensive,
// forcing a reflow always.
// First try a layout without a horizontal scrollbar, then with, except that
// if RELATIVE_HEIGHT is set then for performance we should try the status quo
// first. If RELATIVE_HEIGHT is not set then trying no horizontal scrollbar
// first is almost free.
PRBool firstHScrollbarState =
(mInner.mScrolledFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT)
? aState->mReflowedContentsWithHScrollbar : PR_FALSE;
if (TryLayout(aState, &kidDesiredSize, firstHScrollbarState,
aState->mReflowedContentsWithVScrollbar, PR_FALSE, &rv))
return NS_OK;
// XXX Adding a horizontal scrollbar could cause absolute children positioned
// relative to the bottom padding-edge to need to be reflowed. But we don't,
// because that would be slow.
if (TryLayout(aState, kidDesiredSize, didUseScrollbar, PR_TRUE, PR_FALSE))
if (TryLayout(aState, &kidDesiredSize, !firstHScrollbarState,
aState->mReflowedContentsWithVScrollbar, PR_FALSE, &rv))
return NS_OK;
PRBool canHaveVerticalScrollbar =
aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
// That didn't work. Try the other setting for the vertical scrollbar.
// But don't try to show a scrollbar if we know there can't be one.
if (currentlyUsingVScrollbar || canHaveVerticalScrollbar) {
nsHTMLReflowMetrics kidRetrySize(aDesiredSize.mFlags);
rv = ReflowScrolledFrame(*aState, PR_FALSE, !currentlyUsingVScrollbar,
&kidRetrySize, PR_FALSE);
if (NS_FAILED(rv))
return rv;
didUseScrollbar = !currentlyUsingVScrollbar;
// XXX Adding a horizontal scrollbar could cause absolute children positioned
// relative to the bottom padding-edge to need to be reflowed. But we don't,
// because that would be slow.
if (TryLayout(aState, kidRetrySize, didUseScrollbar, PR_FALSE, PR_FALSE))
return NS_OK;
if (TryLayout(aState, kidRetrySize, didUseScrollbar, PR_TRUE, PR_FALSE))
return NS_OK;
// OK, now try toggling the vertical scrollbar. The performance advantage
// of trying the status-quo horizontal scrollbar state for RELATIVE_HEIGHT cases
// does not exist here (we'll have to reflow due to the vertical scrollbar
// change), so always try no horizontal scrollbar first.
PRBool newVScrollbarState = !aState->mReflowedContentsWithVScrollbar;
if (TryLayout(aState, &kidDesiredSize, PR_FALSE, newVScrollbarState, PR_FALSE, &rv))
return NS_OK;
if (TryLayout(aState, &kidDesiredSize, PR_TRUE, newVScrollbarState, PR_FALSE, &rv))
return NS_OK;
NS_WARNING("Strange content ... we can't find logically consistent scrollbar settings");
} else {
NS_WARNING("Strange content ... we can't find logically consistent scrollbar settings");
}
// Fall back to no scrollbars --- even if NS_STYLE_OVERFLOW_SCROLL is
// in effect. They might not fit anyway.
if (didUseScrollbar) {
rv = ReflowScrolledFrame(*aState, PR_FALSE, PR_FALSE, &kidDesiredSize, PR_FALSE);
if (NS_FAILED(rv))
return rv;
}
TryLayout(aState, kidDesiredSize, PR_FALSE, PR_FALSE, PR_TRUE);
return NS_OK;
// OK, we're out of ideas. Try again with both scrollbars and force the layout
// to stick even if it's inconsistent. This just happens sometimes.
TryLayout(aState, &kidDesiredSize, PR_TRUE, PR_TRUE, PR_TRUE, &rv);
return rv;
}
void

View File

@@ -262,10 +262,10 @@ public:
}
PRBool TryLayout(ScrollReflowState* aState,
const nsHTMLReflowMetrics& aKidMetrics,
nsHTMLReflowMetrics* aKidMetrics,
PRBool aAssumeVScroll, PRBool aAssumeHScroll,
PRBool aForce);
nsresult ReflowScrolledFrame(const ScrollReflowState& aState,
PRBool aForce, nsresult* aResult);
nsresult ReflowScrolledFrame(ScrollReflowState* aState,
PRBool aAssumeHScroll,
PRBool aAssumeVScroll,
nsHTMLReflowMetrics* aMetrics,

View File

@@ -362,16 +362,7 @@ IsQuirkContainingBlockHeight(const nsHTMLReflowState* rs)
nsIAtom* frameType = rs->frame->GetType();
if (nsGkAtoms::blockFrame == frameType ||
nsGkAtoms::areaFrame == frameType ||
nsGkAtoms::scrollFrame == frameType) {
if (nsGkAtoms::areaFrame == frameType) {
// Skip over scrolled-content area frames
if (rs->frame->GetStyleContext()->GetPseudoType() ==
nsCSSAnonBoxes::scrolledContent) {
return PR_FALSE;
}
}
nsGkAtoms::scrollFrame == frameType) {
// Note: This next condition could change due to a style change,
// but that would cause a style reflow anyway, which means we're ok.
if (NS_AUTOHEIGHT == rs->ComputedHeight()) {
@@ -424,7 +415,7 @@ nsHTMLReflowState::InitResizeFlags(nsPresContext* aPresContext)
mStylePosition->mMinHeight.GetUnit() == eStyleUnit_Percent ||
mStylePosition->mMaxHeight.GetUnit() == eStyleUnit_Percent ||
mStylePosition->mOffset.GetTopUnit() == eStyleUnit_Percent ||
mStylePosition->mOffset.GetBottomUnit() == eStyleUnit_Percent ||
mStylePosition->mOffset.GetBottomUnit() != eStyleUnit_Auto ||
frame->IsBoxFrame() ||
frame->GetIntrinsicSize().height.GetUnit() == eStyleUnit_Percent;
@@ -1417,14 +1408,6 @@ CalcQuirkContainingBlockHeight(const nsHTMLReflowState* aCBReflowState)
if (nsGkAtoms::blockFrame == frameType ||
nsGkAtoms::areaFrame == frameType ||
nsGkAtoms::scrollFrame == frameType) {
if (nsGkAtoms::areaFrame == frameType) {
// Skip over scrolled-content area frames
if (rs->frame->GetStyleContext()->GetPseudoType() ==
nsCSSAnonBoxes::scrolledContent) {
continue;
}
}
secondAncestorRS = firstAncestorRS;
firstAncestorRS = (nsHTMLReflowState*)rs;
@@ -1442,12 +1425,7 @@ CalcQuirkContainingBlockHeight(const nsHTMLReflowState* aCBReflowState)
}
}
else if (nsGkAtoms::canvasFrame == frameType) {
// Use scroll frames' computed height if we have one, this will
// allow us to get viewport height for native scrollbars.
nsHTMLReflowState* scrollState = (nsHTMLReflowState *)rs->parentReflowState;
if (nsGkAtoms::scrollFrame == scrollState->frame->GetType()) {
rs = scrollState;
}
// Always continue on to the height calculation
}
else if (nsGkAtoms::pageContentFrame == frameType) {
nsIFrame* prevInFlow = rs->frame->GetPrevInFlow();
@@ -1668,24 +1646,14 @@ nsHTMLReflowState::InitConstraints(nsPresContext* aPresContext,
// content
nsIAtom* fType;
if (NS_AUTOHEIGHT == aContainingBlockHeight) {
// See if the containing block is (1) a scrolled frame, i.e. its
// parent is a scroll frame. The presence of the intervening
// frame (that the scroll frame scrolls) needs to be hidden from
// the containingBlockHeight calcuation, or (2) a cell frame which needs
// See if the containing block is a cell frame which needs
// to use the mComputedHeight of the cell instead of what the cell block passed in.
// XXX It seems like this could lead to bugs with min-height and friends
if (cbrs->parentReflowState) {
nsIFrame* f = cbrs->parentReflowState->frame;
fType = f->GetType();
if (nsGkAtoms::scrollFrame == fType) {
// Use the scroll frame's computed height instead
aContainingBlockHeight = cbrs->parentReflowState->mComputedHeight;
}
else {
fType = cbrs->frame->GetType();
if (IS_TABLE_CELL(fType)) {
// use the cell's computed height
aContainingBlockHeight = cbrs->mComputedHeight;
}
fType = cbrs->frame->GetType();
if (IS_TABLE_CELL(fType)) {
// use the cell's computed height
aContainingBlockHeight = cbrs->mComputedHeight;
}
}
}

View File

@@ -80,3 +80,7 @@ include ib-split/reftest.list
# line-breaking
include line-breaking/reftest.list
# percent-overflow-sizing
include percent-overflow-sizing/reftest.list