From 1712c24093d43b0c11b76471e62e318531071ab8 Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Tue, 2 Oct 2012 10:54:46 -0400 Subject: [PATCH] b=731974, requestAnimationFrame generates too short/long frames; r=bz,smaug,roc --- layout/base/nsRefreshDriver.cpp | 644 +++++++++++++++++++++++++------ layout/base/nsRefreshDriver.h | 47 ++- layout/build/nsLayoutStatics.cpp | 2 + xpcom/ds/TimeStamp.h | 12 + 4 files changed, 560 insertions(+), 145 deletions(-) diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index d0fae4a59572..62a07d86affa 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -7,11 +7,27 @@ /* * Code to notify things that animate before a refresh, at an appropriate * refresh rate. (Perhaps temporary, until replaced by compositor.) + * + * Chrome and each tab have their own RefreshDriver, which in turn + * hooks into one of a few global timer based on RefreshDriverTimer, + * defined below. There are two main global timers -- one for active + * animations, and one for inactive ones. These are implemented as + * subclasses of RefreshDriverTimer; see below for a description of + * their implementations. In the future, additional timer types may + * implement things like blocking on vsync. */ +#ifdef XP_WIN +#include +// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have +// to manually include it +#include +#endif + #include "mozilla/Util.h" #include "nsRefreshDriver.h" +#include "nsITimer.h" #include "nsPresContext.h" #include "nsComponentManagerUtils.h" #include "prlog.h" @@ -31,17 +47,386 @@ using mozilla::TimeDuration; using namespace mozilla; +#ifdef PR_LOGGING +static PRLogModuleInfo *gLog = nullptr; +#define LOG(...) PR_LOG(gLog, PR_LOG_NOTICE, (__VA_ARGS__)) +#else +#define LOG(...) do { } while(0) +#endif + #define DEFAULT_FRAME_RATE 60 #define DEFAULT_THROTTLED_FRAME_RATE 1 +// after 10 minutes, stop firing off inactive timers +#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600 -static bool sPrecisePref; +namespace mozilla { + +/* + * The base class for all global refresh driver timers. It takes care + * of managing the list of refresh drivers attached to them and + * provides interfaces for querying/setting the rate and actually + * running a timer 'Tick'. Subclasses must implement StartTimer(), + * StopTimer(), and ScheduleNextTick() -- the first two just + * start/stop whatever timer mechanism is in use, and ScheduleNextTick + * is called at the start of the Tick() implementation to set a time + * for the next tick. + */ +class RefreshDriverTimer { +public: + /* + * aRate -- the delay, in milliseconds, requested between timer firings + */ + RefreshDriverTimer(double aRate) + { + SetRate(aRate); + } + + virtual ~RefreshDriverTimer() + { + NS_ASSERTION(mRefreshDrivers.Length() == 0, "Should have removed all refresh drivers from here by now!"); + } + + virtual void AddRefreshDriver(nsRefreshDriver* aDriver) + { + NS_ASSERTION(!mRefreshDrivers.Contains(aDriver), "AddRefreshDriver for a refresh driver that's already in the list!"); + mRefreshDrivers.AppendElement(aDriver); + + if (mRefreshDrivers.Length() == 1) { + StartTimer(); + } + } + + virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver) + { + NS_ASSERTION(mRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the list!"); + mRefreshDrivers.RemoveElement(aDriver); + + if (mRefreshDrivers.Length() == 0) { + StopTimer(); + } + } + + double GetRate() const + { + return mRateMilliseconds; + } + + // will take effect at next timer tick + virtual void SetRate(double aNewRate) + { + mRateMilliseconds = aNewRate; + mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds); + } + + TimeStamp MostRecentRefresh() const { return mLastFireTime; } + int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; } + +protected: + virtual void StartTimer() = 0; + virtual void StopTimer() = 0; + virtual void ScheduleNextTick(TimeStamp aNowTime) = 0; + + /* + * Actually runs a tick, poking all the attached RefreshDrivers. + * Grabs the "now" time via JS_Now and TimeStamp::Now(). + */ + void Tick() + { + int64_t jsnow = JS_Now(); + TimeStamp now = TimeStamp::Now(); + + ScheduleNextTick(now); + + mLastFireEpoch = jsnow; + mLastFireTime = now; + + nsTArray > drivers(mRefreshDrivers); + for (size_t i = 0; i < drivers.Length(); ++i) { + // don't poke this driver if it's in test mode + if (drivers[i]->IsTestControllingRefreshesEnabled()) { + continue; + } + + TickDriver(drivers[i], jsnow, now); + } + } + + static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now) + { + driver->Tick(jsnow, now); + } + + double mRateMilliseconds; + TimeDuration mRateDuration; + + int64_t mLastFireEpoch; + TimeStamp mLastFireTime; + TimeStamp mTargetTime; + + nsTArray > mRefreshDrivers; + + // useful callback for nsITimer-based derived classes, here + // bacause of c++ protected shenanigans + static void TimerTick(nsITimer* aTimer, void* aClosure) + { + RefreshDriverTimer *timer = static_cast(aClosure); + timer->Tick(); + } +}; + +/* + * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that + * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to + * implement ScheduleNextTick and intelligently calculate the next time to tick, + * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain + * with its attempt at intelligent slack removal and such, so we don't do it. + */ +class SimpleTimerBasedRefreshDriverTimer : + public RefreshDriverTimer +{ +public: + SimpleTimerBasedRefreshDriverTimer(double aRate) + : RefreshDriverTimer(aRate) + { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + virtual ~SimpleTimerBasedRefreshDriverTimer() + { + StopTimer(); + } + +protected: + + virtual void StartTimer() + { + mLastFireTime = TimeStamp::Now(); + mTargetTime = mLastFireTime; + + mTimer->InitWithFuncCallback(TimerTick, this, 0, nsITimer::TYPE_ONE_SHOT); + } + + virtual void StopTimer() + { + mTimer->Cancel(); + } + + nsRefPtr mTimer; +}; + +/* + * PreciseRefreshDriverTimer schedules ticks based on the current time + * and when the next tick -should- be sent if we were hitting our + * rate. It always schedules ticks on multiples of aRate -- meaning that + * if some execution takes longer than an alloted slot, the next tick + * will be delayed instead of triggering instantly. This might not be + * desired -- there's an #if 0'd block below that we could put behind + * a pref to control this behaviour. + */ +class PreciseRefreshDriverTimer : + public SimpleTimerBasedRefreshDriverTimer +{ +public: + PreciseRefreshDriverTimer(double aRate) + : SimpleTimerBasedRefreshDriverTimer(aRate) + { + } + +protected: + virtual void ScheduleNextTick(TimeStamp aNowTime) + { + // The number of (whole) elapsed intervals between the last target + // time and the actual time. We want to truncate the double down + // to an int number of intervals. + int numElapsedIntervals = static_cast((aNowTime - mTargetTime) / mRateDuration); + + // the last "tick" that may or may not have been actually sent was + // at this time. For example, if the rate is 15ms, the target + // time is 200ms, and it's now 225ms, the last effective tick + // would have been at 215ms. The next one should then be + // scheduled for 5 ms from now. + // + // We then add another mRateDuration to find the next tick target. + TimeStamp newTarget = mTargetTime + mRateDuration * (numElapsedIntervals + 1); + + // the amount of (integer) ms until the next time we should tick + uint32_t delay = static_cast((newTarget - aNowTime).ToMilliseconds()); + + // Without this block, we'll always schedule on interval ticks; + // with it, we'll schedule immediately if we missed our tick target + // last time. +#if 0 + if (numElapsedIntervals > 0) { + // we're late, so reset + newTarget = aNowTime; + delay = 0; + } +#endif + + // log info & lateness + LOG("[%p] precise timer last tick late by %f ms, next tick in %d ms", + this, + (aNowTime - mTargetTime).ToMilliseconds(), + delay); + + // then schedule the timer + mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); + + mTargetTime = newTarget; + } +}; + +/* + * A RefreshDriverTimer for inactive documents. When a new refresh driver is + * added, the rate is reset to the base (normally 1s/1fps). Every time + * it ticks, a single refresh driver is poked. Once they have all been poked, + * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point, + * the timer is quiet and doesn't tick (until something is added to it again). + * + * When a timer is removed, there is a possibility of another timer + * being skipped for one cycle. We could avoid this by adjusting + * mNextDriverIndex in RemoveRefreshDriver, but there's little need to + * add that complexity. All we want is for inactive drivers to tick + * at some point, but we don't care too much about how often. + */ +class InactiveRefreshDriverTimer : + public RefreshDriverTimer +{ +public: + InactiveRefreshDriverTimer(double aRate) + : RefreshDriverTimer(aRate), + mNextTickDuration(aRate), + mDisableAfterMilliseconds(-1.0), + mNextDriverIndex(0) + { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds) + : RefreshDriverTimer(aRate), + mNextTickDuration(aRate), + mDisableAfterMilliseconds(aDisableAfterMilliseconds), + mNextDriverIndex(0) + { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + + virtual void AddRefreshDriver(nsRefreshDriver* aDriver) + { + RefreshDriverTimer::AddRefreshDriver(aDriver); + + LOG("[%p] inactive timer got new refresh driver %p, resetting rate", + this, aDriver); + + // reset the timer, and start with the newly added one next time. + mNextTickDuration = mRateMilliseconds; + + // we don't really have to start with the newly added one, but we may as well + // not tick the old ones at the fastest rate any more than we need to. + mNextDriverIndex = mRefreshDrivers.Length() - 1; + + StopTimer(); + StartTimer(); + } + +protected: + virtual void StartTimer() + { + mLastFireTime = TimeStamp::Now(); + mTargetTime = mLastFireTime; + + mTimer->InitWithFuncCallback(TimerTickOne, this, 0, nsITimer::TYPE_ONE_SHOT); + } + + virtual void StopTimer() + { + mTimer->Cancel(); + } + + virtual void ScheduleNextTick(TimeStamp aNowTime) + { + if (mDisableAfterMilliseconds > 0.0 && + mNextTickDuration > mDisableAfterMilliseconds) + { + // We hit the time after which we should disable + // inactive window refreshes; don't schedule anything + // until we get kicked by an AddRefreshDriver call. + return; + } + + // double the next tick time if we've already gone through all of them once + if (mNextDriverIndex >= mRefreshDrivers.Length()) { + mNextTickDuration *= 2.0; + mNextDriverIndex = 0; + } + + // this doesn't need to be precise; do a simple schedule + uint32_t delay = static_cast(mNextTickDuration); + mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT); + + LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration, + mNextDriverIndex, mRefreshDrivers.Length()); + } + + /* Runs just one driver's tick. */ + void TickOne() + { + int64_t jsnow = JS_Now(); + TimeStamp now = TimeStamp::Now(); + + ScheduleNextTick(now); + + mLastFireEpoch = jsnow; + mLastFireTime = now; + + nsTArray > drivers(mRefreshDrivers); + if (mNextDriverIndex < drivers.Length() && + !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled()) + { + TickDriver(drivers[mNextDriverIndex], jsnow, now); + } + + mNextDriverIndex++; + } + + static void TimerTickOne(nsITimer* aTimer, void* aClosure) + { + InactiveRefreshDriverTimer *timer = static_cast(aClosure); + timer->TickOne(); + } + + nsRefPtr mTimer; + double mNextTickDuration; + double mDisableAfterMilliseconds; + uint32_t mNextDriverIndex; +}; + +} // namespace mozilla + +static PreciseRefreshDriverTimer *sRegularRateTimer = nullptr; +static InactiveRefreshDriverTimer *sThrottledRateTimer = nullptr; + +static int32_t sHighPrecisionTimerRequests = 0; /* static */ void nsRefreshDriver::InitializeStatics() { - Preferences::AddBoolVarCache(&sPrecisePref, - "layout.frame_rate.precise", - false); +#ifdef PR_LOGGING + if (!gLog) { + gLog = PR_NewLogModule("nsRefreshDriver"); + } +#endif +} + +/* static */ void +nsRefreshDriver::Shutdown() +{ + // clean up our timers + delete sRegularRateTimer; + delete sThrottledRateTimer; + + sRegularRateTimer = nullptr; + sThrottledRateTimer = nullptr; } /* static */ int32_t @@ -52,46 +437,60 @@ nsRefreshDriver::DefaultInterval() // Compute the interval to use for the refresh driver timer, in // milliseconds -int32_t -nsRefreshDriver::GetRefreshTimerInterval() const +double +nsRefreshDriver::GetRegularTimerInterval() const { - const char* prefName = - mThrottled ? "layout.throttled_frame_rate" : "layout.frame_rate"; - int32_t rate = Preferences::GetInt(prefName, -1); + int32_t rate = Preferences::GetInt("layout.frame_rate", -1); if (rate <= 0) { // TODO: get the rate from the platform - rate = mThrottled ? DEFAULT_THROTTLED_FRAME_RATE : DEFAULT_FRAME_RATE; + rate = DEFAULT_FRAME_RATE; } - NS_ASSERTION(rate > 0, "Must have positive rate here"); - int32_t interval = NSToIntRound(1000.0/rate); - if (mThrottled) { - interval = NS_MAX(interval, mLastTimerInterval * 2); - } - mLastTimerInterval = interval; - return interval; + return 1000.0 / rate; } -int32_t -nsRefreshDriver::GetRefreshTimerType() const +double +nsRefreshDriver::GetThrottledTimerInterval() const +{ + int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1); + if (rate <= 0) { + rate = DEFAULT_THROTTLED_FRAME_RATE; + } + return 1000.0 / rate; +} + +double +nsRefreshDriver::GetRefreshTimerInterval() const +{ + return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval(); +} + +RefreshDriverTimer* +nsRefreshDriver::ChooseTimer() const { if (mThrottled) { - return nsITimer::TYPE_ONE_SHOT; + if (!sThrottledRateTimer) + sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(), + DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0); + return sThrottledRateTimer; } - if (HaveFrameRequestCallbacks() || sPrecisePref) { - return nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP; - } - return nsITimer::TYPE_REPEATING_SLACK; + + if (!sRegularRateTimer) + sRegularRateTimer = new PreciseRefreshDriverTimer(GetRegularTimerInterval()); + return sRegularRateTimer; } -nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext) +nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) : mPresContext(aPresContext), + mActiveTimer(nullptr), mFrozen(false), mThrottled(false), mTestControllingRefreshes(false), - mTimerIsPrecise(false), mViewManagerFlushIsPending(false), - mLastTimerInterval(0) + mRequestedHighPrecision(false) { + mMostRecentRefreshEpochTime = JS_Now(); + mMostRecentRefresh = TimeStamp::Now(); + mRequests.Init(); } @@ -99,7 +498,7 @@ nsRefreshDriver::~nsRefreshDriver() { NS_ABORT_IF_FALSE(ObserverCount() == 0, "observers should have unregistered"); - NS_ABORT_IF_FALSE(!mTimer, "timer should be gone"); + NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone"); for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); @@ -112,12 +511,22 @@ nsRefreshDriver::~nsRefreshDriver() void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) { - mTestControllingRefreshes = true; + // ensure that we're removed from our driver + StopTimer(); + + if (!mTestControllingRefreshes) { + mMostRecentRefreshEpochTime = JS_Now(); + mMostRecentRefresh = TimeStamp::Now(); + + mTestControllingRefreshes = true; + } + mMostRecentRefreshEpochTime += aMilliseconds * 1000; - mMostRecentRefresh += TimeDuration::FromMilliseconds(aMilliseconds); + mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds); + nsCxPusher pusher; if (pusher.PushNull()) { - Notify(nullptr); + DoTick(); pusher.Pop(); } } @@ -126,31 +535,23 @@ void nsRefreshDriver::RestoreNormalRefresh() { mTestControllingRefreshes = false; - nsCxPusher pusher; - if (pusher.PushNull()) { - Notify(nullptr); // will call UpdateMostRecentRefresh() - pusher.Pop(); - } + EnsureTimerStarted(false); } TimeStamp nsRefreshDriver::MostRecentRefresh() const { - const_cast(this)->EnsureTimerStarted(false); - return mMostRecentRefresh; } int64_t nsRefreshDriver::MostRecentRefreshEpochTime() const { - const_cast(this)->EnsureTimerStarted(false); - return mMostRecentRefreshEpochTime; } bool -nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver, +nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -162,7 +563,7 @@ nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver, } bool -nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver, +nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -195,47 +596,67 @@ void nsRefreshDriver::ClearAllImageRequests() void nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer) { - if (mTimer || mFrozen || !mPresContext) { - // It's already been started, or we don't want to start it now or - // we've been disconnected. + if (mTestControllingRefreshes) + return; + + // if the only change that's needed is that we need high precision, + // then just set that + if (!mThrottled && !mRequestedHighPrecision && mFrameRequestCallbackDocs.Length() > 0) { + SetHighPrecisionTimersEnabled(true); + } else if (mRequestedHighPrecision) { + SetHighPrecisionTimersEnabled(false); + } + + // will it already fire, and no other changes needed? + if (mActiveTimer && !aAdjustingTimer) + return; + + if (mFrozen || !mPresContext) { + // If we don't want to start it now, or we've been disconnected. + StopTimer(); return; } - if (!aAdjustingTimer) { - // If we didn't already have a timer and aAdjustingTimer is false, - // then we just got our first observer (or an explicit call to - // MostRecentRefresh by a caller who's likely to add an observer - // shortly). This means we should fake a most-recent-refresh time - // of now so that said observer gets a reasonable refresh time, so - // things behave as though the timer had always been running. - UpdateMostRecentRefresh(); - } + // we got here because we're either adjusting the time *or* we're + // starting it for the first time; make sure it's stopped. + StopTimer(); - mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - if (!mTimer) { - return; - } - - int32_t timerType = GetRefreshTimerType(); - mTimerIsPrecise = (timerType == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); - - nsresult rv = mTimer->InitWithCallback(this, - GetRefreshTimerInterval(), - timerType); - if (NS_FAILED(rv)) { - mTimer = nullptr; - } + mActiveTimer = ChooseTimer(); + mActiveTimer->AddRefreshDriver(this); } void nsRefreshDriver::StopTimer() { - if (!mTimer) { + if (!mActiveTimer) return; - } - mTimer->Cancel(); - mTimer = nullptr; + mActiveTimer->RemoveRefreshDriver(this); + mActiveTimer = nullptr; + + if (mRequestedHighPrecision) { + SetHighPrecisionTimersEnabled(false); + } +} + +void +nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable) +{ + if (aEnable) { + NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!"); +#ifdef XP_WIN + if (++sHighPrecisionTimerRequests == 1) + timeBeginPeriod(1); +#endif + mRequestedHighPrecision = true; + } else { + NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!"); +#ifdef XP_WIN + if (--sHighPrecisionTimerRequests == 0) + timeEndPeriod(1); +#endif + mRequestedHighPrecision = false; + } } uint32_t @@ -263,18 +684,6 @@ nsRefreshDriver::ImageRequestCount() const return mRequests.Count(); } -void -nsRefreshDriver::UpdateMostRecentRefresh() -{ - if (mTestControllingRefreshes) { - return; - } - - // Call JS_Now first, since that can have nonzero latency in some rare cases. - mMostRecentRefreshEpochTime = JS_Now(); - mMostRecentRefresh = TimeStamp::Now(); -} - nsRefreshDriver::ObserverArray& nsRefreshDriver::ArrayFor(mozFlushType aFlushType) { @@ -295,28 +704,34 @@ nsRefreshDriver::ArrayFor(mozFlushType aFlushType) * nsISupports implementation */ -NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsITimerCallback) +NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsISupports) /* * nsITimerCallback implementation */ -NS_IMETHODIMP -nsRefreshDriver::Notify(nsITimer *aTimer) +void +nsRefreshDriver::DoTick() { - SAMPLE_LABEL("nsRefreshDriver", "Notify"); - NS_PRECONDITION(!mFrozen, "Why are we notified while frozen?"); NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?"); NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), "Shouldn't have a JSContext on the stack"); - if (mTestControllingRefreshes && aTimer) { - // Ignore real refreshes from our timer (but honor the others). - return NS_OK; + if (mTestControllingRefreshes) { + Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh); + } else { + Tick(JS_Now(), TimeStamp::Now()); } +} - UpdateMostRecentRefresh(); +void +nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) +{ + SAMPLE_LABEL("nsRefreshDriver", "Tick"); + + mMostRecentRefresh = aNowTime; + mMostRecentRefreshEpochTime = aNowEpoch; nsCOMPtr presShell = mPresContext->GetPresShell(); if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { @@ -328,7 +743,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer) // wait until we get a Notify() call when we have no observers // before stopping the timer. StopTimer(); - return NS_OK; + return; } /* @@ -341,11 +756,11 @@ nsRefreshDriver::Notify(nsITimer *aTimer) ObserverArray::EndLimitedIterator etor(mObservers[i]); while (etor.HasMore()) { nsRefPtr obs = etor.GetNext(); - obs->WillRefresh(mMostRecentRefresh); + obs->WillRefresh(aNowTime); if (!mPresContext || !mPresContext->GetPresShell()) { StopTimer(); - return NS_OK; + return; } } @@ -360,7 +775,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer) // readded as needed. mFrameRequestCallbackDocs.Clear(); - int64_t eventTime = mMostRecentRefreshEpochTime / PR_USEC_PER_MSEC; + int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) { nsAutoMicroTask mt; frameRequestCallbacks[i]->Sample(eventTime); @@ -412,10 +827,9 @@ nsRefreshDriver::Notify(nsITimer *aTimer) * for refresh events. */ - ImageRequestParameters parms = {mMostRecentRefresh}; + ImageRequestParameters parms = {aNowTime}; if (mRequests.Count()) { mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms); - EnsureTimerStarted(false); } for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { @@ -433,25 +847,6 @@ nsRefreshDriver::Notify(nsITimer *aTimer) printf("Ending ProcessPendingUpdates\n"); #endif } - - if (mThrottled || - (mTimerIsPrecise != - (GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP))) { - // Stop the timer now and restart it here. Stopping is in the mThrottled - // case ok because either it's already one-shot, and it just fired, and all - // we need to do is null it out, or it's repeating and we need to reset it - // to be one-shot. Stopping and restarting in the case when we need to - // switch from precise to slack timers or vice versa is unfortunately - // required. - - // Note that the EnsureTimerStarted() call here is ok because - // EnsureTimerStarted makes sure to not start the timer if it shouldn't be - // started. - StopTimer(); - EnsureTimerStarted(true); - } - - return NS_OK; } PLDHashOperator @@ -500,10 +895,9 @@ nsRefreshDriver::SetThrottled(bool aThrottled) { if (aThrottled != mThrottled) { mThrottled = aThrottled; - if (mTimer) { + if (mActiveTimer) { // We want to switch our timer type here, so just stop and // restart the timer. - StopTimer(); EnsureTimerStarted(true); } } @@ -513,14 +907,14 @@ void nsRefreshDriver::DoRefresh() { // Don't do a refresh unless we're in a state where we should be refreshing. - if (!mFrozen && mPresContext && mTimer) { - Notify(nullptr); + if (!mFrozen && mPresContext && mActiveTimer) { + DoTick(); } } #ifdef DEBUG bool -nsRefreshDriver::IsRefreshObserver(nsARefreshObserver *aObserver, +nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -544,8 +938,8 @@ nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) mFrameRequestCallbackDocs.NoIndex, "Don't schedule the same document multiple times"); mFrameRequestCallbackDocs.AppendElement(aDocument); - // No need to worry about restarting our timer in precise mode if it's - // already running; that will happen automatically when it fires. + + // make sure that the timer is running EnsureTimerStarted(false); } diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index 0347253bd869..5f5af4d64783 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -13,7 +14,6 @@ #include "mozilla/TimeStamp.h" #include "mozFlushType.h" -#include "nsITimer.h" #include "nsCOMPtr.h" #include "nsTObserverArray.h" #include "nsTArray.h" @@ -32,6 +32,10 @@ class imgIRequest; * notified at refresh times. When nothing needs to be painted, callers * may not be notified. */ +namespace mozilla { + class RefreshDriverTimer; +} + class nsARefreshObserver { public: // AddRef and Release signatures that match nsISupports. Implementors @@ -46,25 +50,28 @@ public: virtual void WillRefresh(mozilla::TimeStamp aTime) = 0; }; -class nsRefreshDriver MOZ_FINAL : public nsITimerCallback { +class nsRefreshDriver MOZ_FINAL : public nsISupports { public: nsRefreshDriver(nsPresContext *aPresContext); ~nsRefreshDriver(); static void InitializeStatics(); + static void Shutdown(); // nsISupports implementation NS_DECL_ISUPPORTS - // nsITimerCallback implementation - NS_DECL_NSITIMERCALLBACK - /** * Methods for testing, exposed via nsIDOMWindowUtils. See * nsIDOMWindowUtils.advanceTimeAndRefresh for description. */ void AdvanceTimeAndRefresh(int64_t aMilliseconds); void RestoreNormalRefresh(); + void DoTick(); + bool IsTestControllingRefreshesEnabled() const + { + return mTestControllingRefreshes; + } /** * Return the time of the most recent refresh. This is intended to be @@ -226,6 +233,8 @@ private: typedef nsTObserverArray ObserverArray; typedef nsTHashtable RequestTable; + void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); + void EnsureTimerStarted(bool aAdjustingTimer); void StopTimer(); @@ -233,22 +242,20 @@ private: uint32_t ImageRequestCount() const; static PLDHashOperator ImageRequestEnumerator(nsISupportsHashKey* aEntry, void* aUserArg); - void UpdateMostRecentRefresh(); ObserverArray& ArrayFor(mozFlushType aFlushType); // Trigger a refresh immediately, if haven't been disconnected or frozen. void DoRefresh(); - int32_t GetRefreshTimerInterval() const; - int32_t GetRefreshTimerType() const; + double GetRefreshTimerInterval() const; + double GetRegularTimerInterval() const; + double GetThrottledTimerInterval() const; bool HaveFrameRequestCallbacks() const { return mFrameRequestCallbackDocs.Length() != 0; } - nsCOMPtr mTimer; - mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null - int64_t mMostRecentRefreshEpochTime; // same thing as mMostRecentRefresh, - // but in microseconds since the epoch. + mozilla::RefreshDriverTimer* ChooseTimer() const; + mozilla::RefreshDriverTimer *mActiveTimer; nsPresContext *mPresContext; // weak; pres context passed in constructor // and unset in Disconnect @@ -256,11 +263,11 @@ private: bool mFrozen; bool mThrottled; bool mTestControllingRefreshes; - /* If mTimer is non-null, this boolean indicates whether the timer is - a precise timer. If mTimer is null, this boolean's value can be - anything. */ - bool mTimerIsPrecise; bool mViewManagerFlushIsPending; + bool mRequestedHighPrecision; + + int64_t mMostRecentRefreshEpochTime; + mozilla::TimeStamp mMostRecentRefresh; // separate arrays for each flush type we support ObserverArray mObservers[3]; @@ -272,14 +279,14 @@ private: // nsTArray on purpose, because we want to be able to swap. nsTArray mFrameRequestCallbackDocs; - // This is the last interval we used for our timer. May be 0 if we - // haven't computed a timer interval yet. - mutable int32_t mLastTimerInterval; - // Helper struct for processing image requests struct ImageRequestParameters { mozilla::TimeStamp ts; }; + + friend class mozilla::RefreshDriverTimer; + + void SetHighPrecisionTimersEnabled(bool aEnable); }; #endif /* !defined(nsRefreshDriver_h_) */ diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index ec764dcdffd8..d7c032da9b67 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -359,4 +359,6 @@ nsLayoutStatics::Shutdown() nsDOMMutationObserver::Shutdown(); ContentParent::ShutDown(); + + nsRefreshDriver::Shutdown(); } diff --git a/xpcom/ds/TimeStamp.h b/xpcom/ds/TimeStamp.h index 401ef2ce8304..d80cd059b24f 100644 --- a/xpcom/ds/TimeStamp.h +++ b/xpcom/ds/TimeStamp.h @@ -83,6 +83,18 @@ public: mValue -= aOther.mValue; return *this; } + TimeDuration operator*(const double aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } + TimeDuration operator*(const int32_t aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } + TimeDuration operator*(const uint32_t aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } + TimeDuration operator*(const int64_t aMultiplier) const { + return TimeDuration::FromTicks(mValue * int64_t(aMultiplier)); + } double operator/(const TimeDuration& aOther) { return static_cast(mValue) / aOther.mValue; }