Bug 1393116 - Move double-reflow for sizing-to-content one level lower
After the fix to
bug 1294442 and
bug 1324499, ResizeReflow began to be called
twice for each DOM update in webext popups, and we also artificially re-set the
scroll outside of ResizeReflow to counter the DidDoReflow callback in
nsHTMLScrollFrame setting scrolltop to zero due to the first reflow, which is
done with unconstrained height.
Because of the scrollport being reset we get spurious DOM scroll events.
Replacing the scrollport also interrupts smooth scrolling.
Move the double-reflow down one level into PresShell, doing it before
DidDoReflow is called. The scrollport is no longer reset (causing a spurious
scroll event), and we don't need to replace it (interrupting smooth scrolling).
Also partially fixes
bug 1396034.
MozReview-Commit-ID: HzYITyH4UeW
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -1906,65 +1906,83 @@ PresShell::sPaintSuppressionCallback(nsI
void
PresShell::AsyncResizeEventCallback(nsITimer* aTimer, void* aPresShell)
{
static_cast<PresShell*>(aPresShell)->FireResizeEvent();
}
nsresult
-PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight)
+PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth,
+ nscoord aOldHeight, ResizeReflowOptions aOptions)
{
if (mZoomConstraintsClient) {
// If we have a ZoomConstraintsClient and the available screen area
// changed, then we might need to disable double-tap-to-zoom, so notify
// the ZCC to update itself.
mZoomConstraintsClient->ScreenSizeChanged();
}
if (mMobileViewportManager) {
// If we have a mobile viewport manager, request a reflow from it. It can
// recompute the final CSS viewport and trigger a call to
// ResizeReflowIgnoreOverride if it changed.
mMobileViewportManager->RequestReflow();
return NS_OK;
}
- return ResizeReflowIgnoreOverride(aWidth, aHeight, aOldWidth, aOldHeight);
+ return ResizeReflowIgnoreOverride(aWidth, aHeight, aOldWidth,
+ aOldHeight, aOptions);
}
nsresult
-PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight)
+PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
+ nscoord aOldWidth, nscoord aOldHeight,
+ ResizeReflowOptions aOptions)
{
NS_PRECONDITION(!mIsReflowing, "Shouldn't be in reflow here!");
- // If we don't have a root frame yet, that means we haven't had our initial
- // reflow... If that's the case, and aWidth or aHeight is unconstrained,
- // ignore them altogether.
nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
- if (!rootFrame &&
- (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE)) {
- // We can't do the work needed for SizeToContent without a root
- // frame, and we want to return before setting the visible area.
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
-
- // There isn't anything useful we can do if the initial reflow hasn't happened.
if (!rootFrame) {
+ // If we don't have a root frame yet, that means we haven't had our initial
+ // reflow... If that's the case, and aWidth or aHeight is unconstrained,
+ // ignore them altogether.
+ if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) {
+ // We can't do the work needed for SizeToContent without a root
+ // frame, and we want to return before setting the visible area.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+ // There isn't anything useful we can do if the initial reflow hasn't
+ // happened.
return NS_OK;
}
WritingMode wm = rootFrame->GetWritingMode();
+ const bool shrinkToFit = aOptions == ResizeReflowOptions::eBSizeLimit;
NS_PRECONDITION((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
"shouldn't use unconstrained isize anymore");
-
- const bool isBSizeChanging = wm.IsVertical()
- ? aOldWidth != aWidth
- : aOldHeight != aHeight;
+ NS_PRECONDITION((wm.IsVertical() ? aWidth : aHeight) !=
+ NS_UNCONSTRAINEDSIZE || shrinkToFit,
+ "shouldn't use unconstrained bsize to SizeToContent anymore");
+ bool isBSizeChanging = wm.IsVertical() ? aOldWidth != aWidth
+ : aOldHeight != aHeight;
+ nscoord targetWidth = aWidth;
+ nscoord targetHeight = aHeight;
+
+ if (shrinkToFit) {
+ if (wm.IsVertical()) {
+ targetWidth = NS_UNCONSTRAINEDSIZE;
+ } else {
+ targetHeight = NS_UNCONSTRAINEDSIZE;
+ }
+ isBSizeChanging = true;
+ }
+
+ mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight));
RefPtr<nsViewManager> viewManager = mViewManager;
nsCOMPtr<nsIPresShell> kungFuDeathGrip(this);
if (!GetPresContext()->SuppressingResizeReflow()) {
// Have to make sure that the content notifications are flushed before we
// start messing with the frame model; otherwise we can get content doubling.
mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
@@ -1993,32 +2011,56 @@ PresShell::ResizeReflowIgnoreOverride(ns
WillDoReflow();
// Kick off a top-down reflow
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
nsViewManager::AutoDisableRefresh refreshBlocker(viewManager);
mDirtyRoots.RemoveElement(rootFrame);
DoReflow(rootFrame, true);
- }
+
+ if (shrinkToFit) {
+ const bool reflowAgain = wm.IsVertical() ?
+ mPresContext->GetVisibleArea().width > aWidth :
+ mPresContext->GetVisibleArea().height > aHeight;
+
+ if (reflowAgain) {
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+ DoReflow(rootFrame, true);
+ }
+ }
+ }
+
+ // the first DoReflow above should've set our bsize if it was
+ // NS_UNCONSTRAINEDSIZE, and the isize shouldn't be NS_UNCONSTRAINEDSIZE
+ // anyway
+ NS_ASSERTION(
+ mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE,
+ "width should not be NS_UNCONSTRAINEDSIZE after reflow");
+ NS_ASSERTION(
+ mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE,
+ "height should not be NS_UNCONSTRAINEDSIZE after reflow");
DidDoReflow(true);
}
}
rootFrame = mFrameConstructor->GetRootFrame();
if (rootFrame) {
wm = rootFrame->GetWritingMode();
+ // reflow did not happen; if the reflow happened, our bsize should not be
+ // NS_UNCONSTRAINEDSIZE because DoReflow will fix it up to the same values
+ // as below
if (wm.IsVertical()) {
- if (aWidth == NS_UNCONSTRAINEDSIZE) {
+ if (mPresContext->GetVisibleArea().width == NS_UNCONSTRAINEDSIZE) {
mPresContext->SetVisibleArea(
nsRect(0, 0, rootFrame->GetRect().width, aHeight));
}
} else {
- if (aHeight == NS_UNCONSTRAINEDSIZE) {
+ if (mPresContext->GetVisibleArea().height == NS_UNCONSTRAINEDSIZE) {
mPresContext->SetVisibleArea(
nsRect(0, 0, aWidth, rootFrame->GetRect().height));
}
}
}
if (!mIsDestroying && !mResizeEvent.IsPending() &&
!mAsyncResizeTimerIsActive) {
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -106,18 +106,24 @@ public:
NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
SelectionRegion aRegion,
int16_t aFlags) override;
NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
virtual void BeginObservingDocument() override;
virtual void EndObservingDocument() override;
virtual nsresult Initialize(nscoord aWidth, nscoord aHeight) override;
- virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth = 0, nscoord aOldHeight = 0) override;
- virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight) override;
+ virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight,
+ nscoord aOldWidth = 0, nscoord aOldHeight = 0,
+ ResizeReflowOptions aOptions =
+ ResizeReflowOptions::eBSizeExact) override;
+ virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
+ nscoord aOldWidth, nscoord aOldHeight,
+ ResizeReflowOptions aOptions =
+ ResizeReflowOptions::eBSizeExact) override;
virtual nsIPageSequenceFrame* GetPageSequenceFrame() const override;
virtual nsCanvasFrame* GetCanvasFrame() const override;
virtual void FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
nsFrameState aBitToAdd,
ReflowRootHandling aRootHandling =
eInferFromBitToAdd) override;
virtual void FrameNeedsToContinueReflow(nsIFrame *aFrame) override;
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -3552,47 +3552,26 @@ nsDocumentViewer::GetContentSizeInternal
{
RefPtr<gfxContext> rcx(presShell->CreateReferenceRenderingContext());
prefWidth = root->GetPrefISize(rcx);
}
if (prefWidth > aMaxWidth) {
prefWidth = aMaxWidth;
}
- nsAutoPtr<nsPresState> frameState;
- nsIScrollableFrame *scrollFrame = presShell->GetRootScrollFrameAsScrollable();
- nsIStatefulFrame *statefulFrame = do_QueryFrame(scrollFrame);
- if (statefulFrame) {
- statefulFrame->SaveState(getter_Transfers(frameState));
- }
-
- nsresult rv = presShell->ResizeReflow(prefWidth, NS_UNCONSTRAINEDSIZE);
+ nsresult rv = presShell->ResizeReflow(prefWidth, aMaxHeight, 0, 0,
+ nsIPresShell::ResizeReflowOptions::eBSizeLimit);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsPresContext> presContext;
GetPresContext(getter_AddRefs(presContext));
NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
- // so how big is it?
+ // Protect against bogus returns here
nsRect shellArea = presContext->GetVisibleArea();
- if (shellArea.height > aMaxHeight) {
- // Reflow to max height if we would up too tall.
- rv = presShell->ResizeReflow(prefWidth, aMaxHeight);
- NS_ENSURE_SUCCESS(rv, rv);
-
- shellArea = presContext->GetVisibleArea();
-
- // the first reflow reset our scroll, now set it back
- if (frameState && presShell->GetRootScrollFrameAsScrollable() == scrollFrame) {
- statefulFrame->RestoreState(frameState);
- scrollFrame->ScrollToRestoredPosition();
- }
- }
-
- // Protect against bogus returns here
NS_ENSURE_TRUE(shellArea.width != NS_UNCONSTRAINEDSIZE &&
shellArea.height != NS_UNCONSTRAINEDSIZE,
NS_ERROR_FAILURE);
*aWidth = presContext->AppUnitsToDevPixels(shellArea.width);
*aHeight = presContext->AppUnitsToDevPixels(shellArea.height);
return NS_OK;
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -357,26 +357,40 @@ public:
* The coordinates for aWidth and aHeight must be in standard nscoords.
*
* Callers of this method must hold a reference to this shell that
* is guaranteed to survive through arbitrary script execution.
* Calling Initialize can execute arbitrary script.
*/
virtual nsresult Initialize(nscoord aWidth, nscoord aHeight) = 0;
+ enum class ResizeReflowOptions : uint32_t {
+ // the resulting BSize should be exactly as given
+ eBSizeExact,
+ // the resulting BSize can be less than the given one, producing
+ // shrink-to-fit sizing in the block dimension
+ eBSizeLimit
+ };
/**
* Reflow the frame model into a new width and height. The
* coordinates for aWidth and aHeight must be in standard nscoord's.
*/
- virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight, nscoord aOldWidth = 0, nscoord aOldHeight = 0) = 0;
+ virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight,
+ nscoord aOldWidth = 0, nscoord aOldHeight = 0,
+ ResizeReflowOptions aOptions =
+ ResizeReflowOptions::eBSizeExact) = 0;
/**
* Do the same thing as ResizeReflow but even if ResizeReflowOverride was
* called previously.
*/
- virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight, nscoord aOldWidth, nscoord aOldHeight) = 0;
+ virtual nsresult ResizeReflowIgnoreOverride(
+ nscoord aWidth, nscoord aHeight,
+ nscoord aOldWidth, nscoord aOldHeight,
+ ResizeReflowOptions aOptions =
+ ResizeReflowOptions::eBSizeExact) = 0;
/**
* Returns true if ResizeReflowOverride has been called.
*/
virtual bool GetIsViewportOverridden() = 0;
/**
* Return true if the presshell expects layout flush.