From e62ae2d243189db86b9b4c1b2d6b18f417539965 Mon Sep 17 00:00:00 2001 From: "roc+%cs.cmu.edu" Date: Thu, 25 Aug 2005 00:10:37 +0000 Subject: [PATCH] Bug 293714. Be smart about guessing whether a vertical scrollbar is needed when we do the first real reflow of a scrollframe; remember in global history whether the page needed a scrollbar or not. r+sr=bzbarsky git-svn-id: svn://10.0.0.236/trunk@178883 18797224-902f-48f8-a5cc-f745e15eee43 --- .../src/history/nsSimpleGlobalHistory.cpp | 44 +++++ .../src/history/nsSimpleGlobalHistory.h | 1 + .../docshell/base/nsGlobalHistoryAdapter.cpp | 12 ++ mozilla/docshell/base/nsIGlobalHistory2.idl | 18 +- mozilla/layout/generic/nsGfxScrollFrame.cpp | 156 ++++++++++++++---- mozilla/layout/generic/nsGfxScrollFrame.h | 9 + .../history/src/nsGlobalHistory.cpp | 42 +++++ .../components/history/src/nsGlobalHistory.h | 1 + .../history/src/nsGlobalHistory.cpp | 41 +++++ .../components/history/src/nsGlobalHistory.h | 1 + 10 files changed, 295 insertions(+), 30 deletions(-) diff --git a/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp b/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp index 382ea00ae7e..90652bdf559 100644 --- a/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp +++ b/mozilla/camino/src/history/nsSimpleGlobalHistory.cpp @@ -1606,6 +1606,47 @@ nsSimpleGlobalHistory::MarkPageAsTyped(nsIURI *aURI) return NS_OK; } +NS_IMETHODIMP +nsSimpleGlobalHistory::SetURIGeckoFlags(nsIURI *aURI, PRUint32 aFlags) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = SetRowValue(row, kToken_GeckoFlagsColumn, (PRInt32)aFlags); + SetDirty(); + return rv; +} + +NS_IMETHODIMP +nsSimpleGlobalHistory::GetURIGeckoFlags(nsIURI *aURI, PRUint32* aFlags) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + return rv; + } + + if (!HasCell(mEnv, row, kToken_GeckoFlagsColumn)) + return NS_ERROR_FAILURE; + + PRInt32 val; + mdb_err err = GetRowValue(row, kToken_GeckoFlagsColumn, &val); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + *aFlags = val; + return NS_OK; +} + //---------------------------------------------------------------------- // // nsGlobalHistory @@ -1994,6 +2035,9 @@ nsSimpleGlobalHistory::CreateTokens() err = mStore->StringToToken(mEnv, "Typed", &kToken_TypedColumn); if (err != 0) return NS_ERROR_FAILURE; + err = mStore->StringToToken(mEnv, "GeckoFlags", &kToken_GeckoFlagsColumn); + if (err != 0) return NS_ERROR_FAILURE; + // meta-data tokens err = mStore->StringToToken(mEnv, "LastPageVisited", &kToken_LastPageVisited); err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder); diff --git a/mozilla/camino/src/history/nsSimpleGlobalHistory.h b/mozilla/camino/src/history/nsSimpleGlobalHistory.h index 9c4bd0dc332..23531c87149 100644 --- a/mozilla/camino/src/history/nsSimpleGlobalHistory.h +++ b/mozilla/camino/src/history/nsSimpleGlobalHistory.h @@ -303,6 +303,7 @@ protected: mdb_column kToken_HostnameColumn; mdb_column kToken_HiddenColumn; mdb_column kToken_TypedColumn; + mdb_column kToken_GeckoFlagsColumn; // meta-data tokens mdb_column kToken_LastPageVisited; diff --git a/mozilla/docshell/base/nsGlobalHistoryAdapter.cpp b/mozilla/docshell/base/nsGlobalHistoryAdapter.cpp index de25ed57a8e..09b46dd085c 100644 --- a/mozilla/docshell/base/nsGlobalHistoryAdapter.cpp +++ b/mozilla/docshell/base/nsGlobalHistoryAdapter.cpp @@ -197,3 +197,15 @@ nsGlobalHistoryAdapter::SetPageTitle(nsIURI* aURI, const nsAString& aTitle) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsGlobalHistoryAdapter::SetURIGeckoFlags(nsIURI *aURI, PRUint32 aFlags) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistoryAdapter::GetURIGeckoFlags(nsIURI *aURI, PRUint32* aFlags) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mozilla/docshell/base/nsIGlobalHistory2.idl b/mozilla/docshell/base/nsIGlobalHistory2.idl index c2cca33b4b5..265605c3fe5 100644 --- a/mozilla/docshell/base/nsIGlobalHistory2.idl +++ b/mozilla/docshell/base/nsIGlobalHistory2.idl @@ -51,9 +51,12 @@ interface nsIURI; // the nsIURI is the subject #define NS_LINK_VISITED_EVENT_TOPIC "link-visited" +// This is NOT part of the interface! It could change. +#define NS_GECKO_FLAG_NEEDS_VERTICAL_SCROLLBAR (1 << 0) + %} -[scriptable, uuid(cf777d42-1270-4b34-be7b-2931c93feda5)] +[scriptable, uuid(fbcfa5ca-5d05-4726-b325-d0e6563a1715)] interface nsIGlobalHistory2 : nsISupports { /** @@ -89,4 +92,17 @@ interface nsIGlobalHistory2 : nsISupports * @param aTitle the page title */ void setPageTitle(in nsIURI aURI, in AString aTitle); + + /** + * Get the Gecko flags for this URI. These flags are used by Gecko as hints + * to optimize page loading. Not all histories have them; this need not be + * supported (just return NS_ERROR_NOT_IMPLEMENTED. These flags are opaque + * and should not be interpreted by the history engine. + */ + unsigned long getURIGeckoFlags(in nsIURI aURI); + /** + * Set the Gecko flags for this URI. May fail if the history entry + * doesn't have any flags or if there is no entry for the URI. + */ + void setURIGeckoFlags(in nsIURI aURI, in unsigned long aFlags); }; diff --git a/mozilla/layout/generic/nsGfxScrollFrame.cpp b/mozilla/layout/generic/nsGfxScrollFrame.cpp index e860e92a24a..68fe1531832 100644 --- a/mozilla/layout/generic/nsGfxScrollFrame.cpp +++ b/mozilla/layout/generic/nsGfxScrollFrame.cpp @@ -75,6 +75,8 @@ #include "nsReflowPath.h" #include "nsAutoPtr.h" #include "nsPresState.h" +#include "nsIGlobalHistory2.h" +#include "nsDocShellCID.h" #ifdef ACCESSIBILITY #include "nsIAccessibilityService.h" #endif @@ -175,8 +177,8 @@ nsHTMLScrollFrame::Destroy(nsPresContext* aPresContext) NS_IMETHODIMP nsHTMLScrollFrame:: SetInitialChildList(nsPresContext* aPresContext, - nsIAtom* aListName, - nsIFrame* aChildList) + nsIAtom* aListName, + nsIFrame* aChildList) { nsresult rv = nsHTMLContainerFrame::SetInitialChildList(aPresContext, aListName, aChildList); @@ -537,36 +539,51 @@ nsHTMLScrollFrame::ReflowScrolledFrame(const ScrollReflowState& aState, return rv; } +PRBool +nsHTMLScrollFrame::GuessVScrollbarNeeded(const ScrollReflowState& aState) +{ + if (aState.mStyles.mVertical != NS_STYLE_OVERFLOW_AUTO) + // no guessing required + return aState.mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL; + + // If we've had at least one non-initial reflow, then just assume + // the state of the vertical scrollbar will be what we determined + // last time. + if (mInner.mHadNonInitialReflow) { + return mInner.mHasVerticalScrollbar; + } + + // If this is the initial reflow, guess PR_FALSE because usually + // we have very little content by then. + if (aState.mReflowState.reason == eReflowReason_Initial) + return PR_FALSE; + + if (mInner.mIsRoot) { + // For viewports, try getting a hint from global history + // as to whether we had a vertical scrollbar last time. + PRBool hint; + nsresult rv = mInner.GetVScrollbarHintFromGlobalHistory(&hint); + if (NS_SUCCEEDED(rv)) + return hint; + // No hint. Assume that there will be a scrollbar; it seems to me + // that 'most pages' do have a scrollbar, and anyway, it's cheaper + // to do an extra reflow for the pages that *don't* need a + // scrollbar (because on average they will have less content). + return PR_TRUE; + } + + // For non-viewports, just guess that we don't need a scrollbar. + // XXX I wonder if statistically this is the right idea; I'm + // basically guessing that there are a lot of overflow:auto DIVs + // that get their intrinsic size and don't overflow + return PR_FALSE; +} + nsresult nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState, const nsHTMLReflowMetrics& aDesiredSize) { - // Try layouts that keep the vertical scrollbar setting the same, - // first. That will minimize the work we have to do. - PRBool currentlyUsingVScrollbar = mInner.mHasVerticalScrollbar; - - if (aState->mReflowState.reason == eReflowReason_Initial) { - // Set initial vertical scrollbar assumption. - if (aState->mStyles.mVertical == NS_STYLE_OVERFLOW_SCROLL) { - currentlyUsingVScrollbar = PR_TRUE; - } else { - // If we're the viewport scrollframe, then let's start out assuming that - // there *is* a vertical scrollbar. - // XXX disable this for now so we can see what the Tp impact of the - // big changes is. - // if (mInner.mIsRoot) { - // currentlyUsingVScrollbar = PR_TRUE; - // } - } - } - - // Don't assume a vertical scrollbar if we're not allowed to have - // one - PRBool canHaveVerticalScrollbar = - aState->mStyles.mVertical != NS_STYLE_OVERFLOW_HIDDEN; - if (!canHaveVerticalScrollbar) - currentlyUsingVScrollbar = PR_FALSE; - + PRBool currentlyUsingVScrollbar = GuessVScrollbarNeeded(*aState); nsHTMLReflowMetrics kidDesiredSize(aDesiredSize.mComputeMEW, aDesiredSize.mFlags); nsresult rv = ReflowScrolledFrame(*aState, currentlyUsingVScrollbar, &kidDesiredSize, PR_TRUE); @@ -614,6 +631,8 @@ nsHTMLScrollFrame::ReflowContents(ScrollReflowState* aState, if (TryLayout(aState, kidDesiredSize, didUseScrollbar, PR_TRUE, PR_FALSE)) 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) { @@ -834,6 +853,15 @@ nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext, aDesiredSize.mOverflowArea = nsRect(0, 0, aDesiredSize.width, aDesiredSize.height); FinishAndStoreOverflow(&aDesiredSize); + if (reason != eReflowReason_Initial && !mInner.mHadNonInitialReflow) { + mInner.mHadNonInitialReflow = PR_TRUE; + if (mInner.mIsRoot) { + // For viewports, record whether we needed a vertical scrollbar + // after the first non-initial reflow. + mInner.SaveVScrollbarStateToGlobalHistory(); + } + } + aStatus = NS_FRAME_COMPLETE; NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); return rv; @@ -1322,7 +1350,10 @@ nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter, PRBool aI mFrameInitiatedScroll(PR_FALSE), mDidHistoryRestore(PR_FALSE), mIsRoot(aIsRoot), - mSupppressScrollbarUpdate(PR_FALSE) + mSupppressScrollbarUpdate(PR_FALSE), + mDidLoadHistoryVScrollbarHint(PR_FALSE), + mHistoryVScrollbarHint(PR_FALSE), + mHadNonInitialReflow(PR_FALSE) { } @@ -2337,6 +2368,9 @@ nsXULScrollFrame::Layout(nsBoxLayoutState& aState) mInner.LayoutScrollbars(aState, clientRect, oldScrollAreaBounds, scrollAreaRect); } mInner.ScrollToRestoredPosition(); + if (aState.GetReflowState()->reason != eReflowReason_Initial) { + mInner.mHadNonInitialReflow = PR_TRUE; + } return NS_OK; } @@ -2548,6 +2582,70 @@ nsGfxScrollFrameInner::GetIntegerAttribute(nsIBox* aBox, nsIAtom* atom, PRInt32 return defaultValue; } +static nsIURI* GetDocURI(nsIFrame* aFrame) +{ + nsIPresShell* shell = aFrame->GetPresContext()->GetPresShell(); + if (!shell) + return nsnull; + nsIDocument* doc = shell->GetDocument(); + if (!doc) + return nsnull; + return doc->GetDocumentURI(); +} + +void +nsGfxScrollFrameInner::SaveVScrollbarStateToGlobalHistory() +{ + NS_ASSERTION(mIsRoot, "Only use this on viewports"); + + // If the hint is the same as the one we loaded, don't bother + // saving it + if (mDidLoadHistoryVScrollbarHint && + (mHistoryVScrollbarHint == mHasVerticalScrollbar)) + return; + + nsIURI* uri = GetDocURI(mOuter); + if (!uri) + return; + + nsCOMPtr history(do_GetService(NS_GLOBALHISTORY2_CONTRACTID)); + if (!history) + return; + + PRUint32 flags = 0; + if (mHasVerticalScrollbar) { + flags |= NS_GECKO_FLAG_NEEDS_VERTICAL_SCROLLBAR; + } + history->SetURIGeckoFlags(uri, flags); + // if it fails, we don't care +} + +nsresult +nsGfxScrollFrameInner::GetVScrollbarHintFromGlobalHistory(PRBool* aVScrollbarNeeded) +{ + NS_ASSERTION(mIsRoot, "Only use this on viewports"); + NS_ASSERTION(!mDidLoadHistoryVScrollbarHint, + "Should only load a hint once, it can be expensive"); + + nsIURI* uri = GetDocURI(mOuter); + if (!uri) + return NS_ERROR_FAILURE; + + nsCOMPtr history(do_GetService(NS_GLOBALHISTORY2_CONTRACTID)); + if (!history) + return NS_ERROR_FAILURE; + + PRUint32 flags; + nsresult rv = history->GetURIGeckoFlags(uri, &flags); + if (NS_FAILED(rv)) + return rv; + + *aVScrollbarNeeded = (flags & NS_GECKO_FLAG_NEEDS_VERTICAL_SCROLLBAR) != 0; + mDidLoadHistoryVScrollbarHint = PR_TRUE; + mHistoryVScrollbarHint = *aVScrollbarNeeded; + return NS_OK; +} + nsPresState* nsGfxScrollFrameInner::SaveState() { diff --git a/mozilla/layout/generic/nsGfxScrollFrame.h b/mozilla/layout/generic/nsGfxScrollFrame.h index 9315957869a..b87f3b9832b 100644 --- a/mozilla/layout/generic/nsGfxScrollFrame.h +++ b/mozilla/layout/generic/nsGfxScrollFrame.h @@ -117,6 +117,8 @@ public: nsPresState* SaveState(); void RestoreState(nsPresState* aState); + void SaveVScrollbarStateToGlobalHistory(); + nsresult GetVScrollbarHintFromGlobalHistory(PRBool* aVScrollbarNeeded); nsIFrame* GetScrolledFrame() const { return mScrolledFrame; } @@ -160,6 +162,12 @@ public: // Is this the scrollframe for the document's viewport? PRPackedBool mIsRoot:1; PRPackedBool mSupppressScrollbarUpdate:1; + // Did we load a hint from global history + // about whether a vertical scrollbar is required? + PRPackedBool mDidLoadHistoryVScrollbarHint:1; + // The value of the hint loaded + PRPackedBool mHistoryVScrollbarHint:1; + PRPackedBool mHadNonInitialReflow:1; }; /** @@ -316,6 +324,7 @@ protected: void SetSuppressScrollbarUpdate(PRBool aSuppress) { mInner.mSupppressScrollbarUpdate = aSuppress; } + PRBool GuessVScrollbarNeeded(const ScrollReflowState& aState); private: friend class nsGfxScrollFrameInner; diff --git a/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp b/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp index 14890287f04..7f3516ee771 100644 --- a/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp +++ b/mozilla/toolkit/components/history/src/nsGlobalHistory.cpp @@ -1463,6 +1463,45 @@ nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI) return SetRowValue(row, kToken_TypedColumn, 1); } +NS_IMETHODIMP +nsGlobalHistory::SetURIGeckoFlags(nsIURI *aURI, PRUint32 aFlags) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + return rv; + } + + return SetRowValue(row, kToken_GeckoFlagsColumn, (PRInt32)aFlags); +} + +NS_IMETHODIMP +nsGlobalHistory::GetURIGeckoFlags(nsIURI *aURI, PRUint32* aFlags) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + return rv; + } + + if (!HasCell(mEnv, row, kToken_GeckoFlagsColumn)) + return NS_ERROR_FAILURE; + + PRInt32 val; + mdb_err err = GetRowValue(row, kToken_GeckoFlagsColumn, &val); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + *aFlags = val; + return NS_OK; +} + //---------------------------------------------------------------------- // // nsGlobalHistory @@ -2943,6 +2982,9 @@ nsGlobalHistory::CreateTokens() err = mStore->StringToToken(mEnv, "Typed", &kToken_TypedColumn); if (err != 0) return NS_ERROR_FAILURE; + err = mStore->StringToToken(mEnv, "GeckoFlags", &kToken_GeckoFlagsColumn); + if (err != 0) return NS_ERROR_FAILURE; + // meta-data tokens err = mStore->StringToToken(mEnv, "LastPageVisited", &kToken_LastPageVisited); err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder); diff --git a/mozilla/toolkit/components/history/src/nsGlobalHistory.h b/mozilla/toolkit/components/history/src/nsGlobalHistory.h index d03ec36dc51..c4f08669a19 100644 --- a/mozilla/toolkit/components/history/src/nsGlobalHistory.h +++ b/mozilla/toolkit/components/history/src/nsGlobalHistory.h @@ -295,6 +295,7 @@ protected: mdb_column kToken_HostnameColumn; mdb_column kToken_HiddenColumn; mdb_column kToken_TypedColumn; + mdb_column kToken_GeckoFlagsColumn; mdb_column kToken_ByteOrder; // meta-data tokens diff --git a/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp b/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp index ad5287899fe..ed5e95c2028 100644 --- a/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp +++ b/mozilla/xpfe/components/history/src/nsGlobalHistory.cpp @@ -1381,6 +1381,44 @@ nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI) return SetRowValue(row, kToken_TypedColumn, 1); } +NS_IMETHODIMP +nsGlobalHistory::SetURIGeckoFlags(nsIURI *aURI, PRUint32 aFlags) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + return rv; + } + + return SetRowValue(row, kToken_GeckoFlagsColumn, (PRInt32)aFlags); +} + +NS_IMETHODIMP +nsGlobalHistory::GetURIGeckoFlags(nsIURI *aURI, PRUint32* aFlags) +{ + nsCAutoString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row)); + if (NS_FAILED(rv)) { + return rv; + } + + if (!HasCell(mEnv, row, kToken_GeckoFlagsColumn)) + return NS_ERROR_FAILURE; + + PRInt32 val; + mdb_err err = GetRowValue(row, kToken_GeckoFlagsColumn, &val); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + *aFlags = val; + return NS_OK; +} //---------------------------------------------------------------------- // @@ -2853,6 +2891,9 @@ nsGlobalHistory::CreateTokens() err = mStore->StringToToken(mEnv, "Typed", &kToken_TypedColumn); if (err != 0) return NS_ERROR_FAILURE; + err = mStore->StringToToken(mEnv, "GeckoFlags", &kToken_GeckoFlagsColumn); + if (err != 0) return NS_ERROR_FAILURE; + // meta-data tokens err = mStore->StringToToken(mEnv, "LastPageVisited", &kToken_LastPageVisited); err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder); diff --git a/mozilla/xpfe/components/history/src/nsGlobalHistory.h b/mozilla/xpfe/components/history/src/nsGlobalHistory.h index 620056ee48a..6f1b49c59a9 100644 --- a/mozilla/xpfe/components/history/src/nsGlobalHistory.h +++ b/mozilla/xpfe/components/history/src/nsGlobalHistory.h @@ -296,6 +296,7 @@ protected: mdb_column kToken_HostnameColumn; mdb_column kToken_HiddenColumn; mdb_column kToken_TypedColumn; + mdb_column kToken_GeckoFlagsColumn; // meta-data tokens mdb_column kToken_LastPageVisited;