--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -222,28 +222,29 @@ nsIAtom*
nsHTMLScrollFrame::GetType() const
{
return nsGkAtoms::scrollFrame;
}
/**
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.
*/
struct ScrollReflowState {
const nsHTMLReflowState& mReflowState;
nsBoxLayoutState mBoxState;
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;
// The size of the inside-border area
nsSize mInsideBorderSize;
// Whether we decided to show the horizontal scrollbar
PRPackedBool mShowHScrollbar;
// Whether we decided to show the vertical scrollbar
@@ -317,26 +318,39 @@ GetScrollbarMetrics(nsBoxLayoutState& aS
* (i.e., the scrollbar fits horizontally)
* 4) the style is SCROLL, or the kid's overflow-area XMost is
* greater than the scrollport width
*
* @param aForce if PR_TRUE, then we just assume the layout is consistent.
*/
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) {
GetScrollbarMetrics(aState->mBoxState, mInner.mVScrollbarBox,
&vScrollbarMinSize,
aAssumeVScroll ? &vScrollbarPrefSize : nsnull, PR_TRUE);
}
nscoord vScrollbarDesiredWidth = aAssumeVScroll ? vScrollbarPrefSize.width : 0;
@@ -351,19 +365,19 @@ nsHTMLScrollFrame::TryLayout(ScrollReflo
}
nscoord hScrollbarDesiredHeight = aAssumeHScroll ? hScrollbarPrefSize.height : 0;
nscoord hScrollbarDesiredWidth = aAssumeHScroll ? hScrollbarPrefSize.width : 0;
// First, compute our inside-border size and scrollport size
// 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),
PR_MAX(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
if (!aForce) {
nsRect scrolledRect = mInner.GetScrolledRect(scrollPortSize);
@@ -403,51 +417,67 @@ nsHTMLScrollFrame::TryLayout(ScrollReflo
if (!mInner.IsScrollbarOnRight()) {
scrollPortOrigin.x += vScrollbarActualWidth;
}
aState->mScrollPortRect = nsRect(scrollPortOrigin, scrollPortSize);
return PR_TRUE;
}
nsresult
-nsHTMLScrollFrame::ReflowScrolledFrame(const ScrollReflowState& aState,
+nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowState* aState,
PRBool aAssumeHScroll,
PRBool aAssumeVScroll,
nsHTMLReflowMetrics* aMetrics,
PRBool aFirstPass)
{
// 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);
}
// We're forcing the padding on our scrolled frame, so let it know what that
// 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,
kidReflowState, 0, 0,
NS_FRAME_NO_MOVE_FRAME | NS_FRAME_NO_MOVE_VIEW, status);
// Don't resize or position the view because we're going to resize
// it to the correct size anyway in PlaceScrollArea. Allowing it to
// resize here would size it to the natural height of the frame,
@@ -462,16 +492,19 @@ nsHTMLScrollFrame::ReflowScrolledFrame(c
// always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
// support the 'outline' property because of this. Rather than fix the world
// right now, just fix up the overflow area if necessary. Note that we don't
// check NS_FRAME_OUTSIDE_CHILDREN because it could be set even though the
// overflow area doesn't include the frame bounds.
aMetrics->mOverflowArea.UnionRect(aMetrics->mOverflowArea,
nsRect(0, 0, aMetrics->width, aMetrics->height));
+ aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
+ aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
+
return rv;
}
PRBool
nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState)
{
if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO)
// no guessing required
@@ -521,23 +554,20 @@ nsHTMLScrollFrame::InInitialReflow() con
// assumption, because our initial reflow is no longer synchronous).
return !mInner.mIsRoot && (GetStateBits() & NS_FRAME_FIRST_REFLOW);
}
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
// should try laying it out without a vertical scrollbar. It will
// usually fit because making the available-width wider will not
// normally make the child taller. (The only situation I can think
// of is when you have a line containing %-width inline replaced
// elements whose percentages sum to more than 100%, so increasing
@@ -545,76 +575,66 @@ nsHTMLScrollFrame::ReflowContents(Scroll
// before.) If we don't treat this case specially, then we will
// decide that showing scrollbars is OK because the content
// overflows when we're showing scrollbars and we won't try to
// remove the vertical scrollbar.
// 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 =
ComputeInsideBorderSize(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
nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowState& aState)
{
nsIView* scrollView = mInner.mScrollableView->View();
nsIViewManager* vm = scrollView->GetViewManager();
vm->MoveViewTo(scrollView, aState.mScrollPortRect.x, aState.mScrollPortRect.y);