diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 83d9320f6cc7..d0dd7495069b 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -1272,6 +1272,26 @@ IsSmoothScrollingEnabled() return PR_FALSE; } +class ScrollFrameActivityTracker : public nsExpirationTracker { +public: + // Wait for 75-100ms between scrolls before we switch the appearance back to + // subpixel AA. That's 4 generations of 25ms each. + enum { TIMEOUT_MS = 25 }; + ScrollFrameActivityTracker() + : nsExpirationTracker(TIMEOUT_MS) {} + ~ScrollFrameActivityTracker() { + AgeAllGenerations(); + } + + virtual void NotifyExpired(nsGfxScrollFrameInner *aObject) { + RemoveObject(aObject); + aObject->mScrollingActive = PR_FALSE; + aObject->mOuter->InvalidateOverflowRect(); + } +}; + +static ScrollFrameActivityTracker *gScrollFrameActivityTracker = nsnull; + nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter, PRBool aIsRoot, PRBool aIsXUL) @@ -1307,6 +1327,14 @@ nsGfxScrollFrameInner::nsGfxScrollFrameInner(nsContainerFrame* aOuter, nsGfxScrollFrameInner::~nsGfxScrollFrameInner() { + if (mActivityExpirationState.IsTracked()) { + gScrollFrameActivityTracker->RemoveObject(this); + } + if (gScrollFrameActivityTracker && + gScrollFrameActivityTracker->IsEmpty()) { + delete gScrollFrameActivityTracker; + gScrollFrameActivityTracker = nsnull; + } delete mAsyncScroll; } @@ -1562,6 +1590,41 @@ InvalidateFixedBackgroundFrames(nsIFrame* aRootFrame, InvalidateFixedBackgroundFramesFromList(&builder, list); } +PRBool nsGfxScrollFrameInner::IsAlwaysActive() const +{ + // The root scrollframe for a non-chrome document which is the direct + // child of a chrome document is always treated as "active". + if (!mIsRoot) + return PR_FALSE; + nsPresContext* presContext = mOuter->PresContext(); + if (presContext->IsChrome()) + return PR_FALSE; + nsIFrame* rootFrame = mOuter->PresContext()->PresShell()->GetRootFrame(); + nsIFrame* rootParent = nsLayoutUtils::GetCrossDocParentFrame(rootFrame); + return !rootParent || rootParent->PresContext()->IsChrome(); +} + +PRBool nsGfxScrollFrameInner::IsScrollingActive() const +{ + return mScrollingActive || IsAlwaysActive(); +} + +void nsGfxScrollFrameInner::MarkActive() +{ + if (IsAlwaysActive()) + return; + + mScrollingActive = PR_TRUE; + if (mActivityExpirationState.IsTracked()) { + gScrollFrameActivityTracker->MarkUsed(this); + } else { + if (!gScrollFrameActivityTracker) { + gScrollFrameActivityTracker = new ScrollFrameActivityTracker(); + } + gScrollFrameActivityTracker->AddObject(this); + } +} + void nsGfxScrollFrameInner::ScrollVisual(nsIntPoint aPixDelta) { nsRootPresContext* rootPresContext = @@ -1595,10 +1658,10 @@ void nsGfxScrollFrameInner::ScrollVisual(nsIntPoint aPixDelta) // to be consistent with the view and frame hierarchy. PRUint32 flags = nsIFrame::INVALIDATE_REASON_SCROLL_REPAINT; nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter); - if (CanScrollWithBlitting(mOuter, displayRoot) && mScrollingActive) { + if (IsScrollingActive() && CanScrollWithBlitting(mOuter, displayRoot)) { flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS; } - mScrollingActive = PR_TRUE; + MarkActive(); mOuter->InvalidateWithFlags(mScrollPort, flags); if (flags & nsIFrame::INVALIDATE_NO_THEBES_LAYERS) { diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 0482a2204965..f00560621014 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -54,6 +54,7 @@ #ifdef MOZ_SVG #include "nsSVGIntegrationUtils.h" #endif +#include "nsExpirationTracker.h" class nsPresContext; class nsIPresShell; @@ -213,7 +214,7 @@ public: nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState); PRBool IsLTR() const; PRBool IsScrollbarOnRight() const; - PRBool IsScrollingActive() { return mScrollingActive; } + PRBool IsScrollingActive() const; // adjust the scrollbar rectangle aRect to account for any visible resizer. // aHasResizer specifies if there is a content resizer, however this method // will also check if a widget resizer is present as well. @@ -227,6 +228,10 @@ public: const nsRect& aContentArea, const nsRect& aOldScrollArea); + PRBool IsAlwaysActive() const; + void MarkActive(); + nsExpirationState* GetExpirationState() { return &mActivityExpirationState; } + // owning references to the nsIAnonymousContentCreator-built content nsCOMPtr mHScrollbarContent; nsCOMPtr mVScrollbarContent; @@ -253,6 +258,8 @@ public: nsPoint mRestorePos; nsPoint mLastPos; + nsExpirationState mActivityExpirationState; + PRPackedBool mNeverHasVerticalScrollbar:1; PRPackedBool mNeverHasHorizontalScrollbar:1; PRPackedBool mHasVerticalScrollbar:1; diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h index 1dcca72d7eac..8a93589a9775 100644 --- a/xpcom/ds/nsExpirationTracker.h +++ b/xpcom/ds/nsExpirationTracker.h @@ -259,12 +259,20 @@ template class nsExpirationTracker { friend class Iterator; + PRBool IsEmpty() { + for (PRUint32 i = 0; i < K; ++i) { + if (!mGenerations[i].IsEmpty()) + return PR_FALSE; + } + return PR_TRUE; + } + protected: /** * This must be overridden to catch notifications. It is called whenever * we detect that an object has not been used for at least (K-1)*mTimerPeriod - * seconds. If timer events are not delayed, it will be called within - * roughly K*mTimerPeriod seconds after the last use. (Unless AgeOneGeneration + * milliseconds. If timer events are not delayed, it will be called within + * roughly K*mTimerPeriod milliseconds after the last use. (Unless AgeOneGeneration * or AgeAllGenerations have been called to accelerate the aging process.) * * NOTE: These bounds ignore delays in timer firings due to actual work being @@ -298,13 +306,10 @@ template class nsExpirationTracker { nsExpirationTracker* tracker = static_cast(aThis); tracker->AgeOneGeneration(); // Cancel the timer if we have no objects to track - PRUint32 i; - for (i = 0; i < K; ++i) { - if (!tracker->mGenerations[i].IsEmpty()) - return; + if (tracker->IsEmpty()) { + tracker->mTimer->Cancel(); + tracker->mTimer = nsnull; } - tracker->mTimer->Cancel(); - tracker->mTimer = nsnull; } nsresult CheckStartTimer() {