diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 62a07d86aff..d0fae4a5957 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -7,27 +7,11 @@ /* * 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" @@ -47,386 +31,17 @@ 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 -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 bool sPrecisePref; /* static */ void nsRefreshDriver::InitializeStatics() { -#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; + Preferences::AddBoolVarCache(&sPrecisePref, + "layout.frame_rate.precise", + false); } /* static */ int32_t @@ -437,60 +52,46 @@ nsRefreshDriver::DefaultInterval() // Compute the interval to use for the refresh driver timer, in // milliseconds -double -nsRefreshDriver::GetRegularTimerInterval() const -{ - int32_t rate = Preferences::GetInt("layout.frame_rate", -1); - if (rate <= 0) { - // TODO: get the rate from the platform - rate = DEFAULT_FRAME_RATE; - } - return 1000.0 / rate; -} - -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 +int32_t nsRefreshDriver::GetRefreshTimerInterval() const { - return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval(); + const char* prefName = + mThrottled ? "layout.throttled_frame_rate" : "layout.frame_rate"; + int32_t rate = Preferences::GetInt(prefName, -1); + if (rate <= 0) { + // TODO: get the rate from the platform + rate = mThrottled ? DEFAULT_THROTTLED_FRAME_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; } -RefreshDriverTimer* -nsRefreshDriver::ChooseTimer() const +int32_t +nsRefreshDriver::GetRefreshTimerType() const { if (mThrottled) { - if (!sThrottledRateTimer) - sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(), - DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0); - return sThrottledRateTimer; + return nsITimer::TYPE_ONE_SHOT; } - - if (!sRegularRateTimer) - sRegularRateTimer = new PreciseRefreshDriverTimer(GetRegularTimerInterval()); - return sRegularRateTimer; + if (HaveFrameRequestCallbacks() || sPrecisePref) { + return nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP; + } + return nsITimer::TYPE_REPEATING_SLACK; } -nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) +nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext) : mPresContext(aPresContext), - mActiveTimer(nullptr), mFrozen(false), mThrottled(false), mTestControllingRefreshes(false), + mTimerIsPrecise(false), mViewManagerFlushIsPending(false), - mRequestedHighPrecision(false) + mLastTimerInterval(0) { - mMostRecentRefreshEpochTime = JS_Now(); - mMostRecentRefresh = TimeStamp::Now(); - mRequests.Init(); } @@ -498,7 +99,7 @@ nsRefreshDriver::~nsRefreshDriver() { NS_ABORT_IF_FALSE(ObserverCount() == 0, "observers should have unregistered"); - NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone"); + NS_ABORT_IF_FALSE(!mTimer, "timer should be gone"); for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); @@ -511,22 +112,12 @@ nsRefreshDriver::~nsRefreshDriver() void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) { - // ensure that we're removed from our driver - StopTimer(); - - if (!mTestControllingRefreshes) { - mMostRecentRefreshEpochTime = JS_Now(); - mMostRecentRefresh = TimeStamp::Now(); - - mTestControllingRefreshes = true; - } - + mTestControllingRefreshes = true; mMostRecentRefreshEpochTime += aMilliseconds * 1000; - mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds); - + mMostRecentRefresh += TimeDuration::FromMilliseconds(aMilliseconds); nsCxPusher pusher; if (pusher.PushNull()) { - DoTick(); + Notify(nullptr); pusher.Pop(); } } @@ -535,23 +126,31 @@ void nsRefreshDriver::RestoreNormalRefresh() { mTestControllingRefreshes = false; - EnsureTimerStarted(false); + nsCxPusher pusher; + if (pusher.PushNull()) { + Notify(nullptr); // will call UpdateMostRecentRefresh() + pusher.Pop(); + } } 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); @@ -563,7 +162,7 @@ nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, } bool -nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, +nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -596,67 +195,47 @@ void nsRefreshDriver::ClearAllImageRequests() void nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer) { - 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(); + if (mTimer || mFrozen || !mPresContext) { + // It's already been started, or we don't want to start it now or + // we've been disconnected. return; } - // 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(); + 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(); + } - mActiveTimer = ChooseTimer(); - mActiveTimer->AddRefreshDriver(this); + 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; + } } void nsRefreshDriver::StopTimer() { - if (!mActiveTimer) + if (!mTimer) { return; - - 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; - } + mTimer->Cancel(); + mTimer = nullptr; } uint32_t @@ -684,6 +263,18 @@ 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) { @@ -704,34 +295,28 @@ nsRefreshDriver::ArrayFor(mozFlushType aFlushType) * nsISupports implementation */ -NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsISupports) +NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsITimerCallback) /* * nsITimerCallback implementation */ -void -nsRefreshDriver::DoTick() +NS_IMETHODIMP +nsRefreshDriver::Notify(nsITimer *aTimer) { + 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) { - Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh); - } else { - Tick(JS_Now(), TimeStamp::Now()); + if (mTestControllingRefreshes && aTimer) { + // Ignore real refreshes from our timer (but honor the others). + return NS_OK; } -} -void -nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) -{ - SAMPLE_LABEL("nsRefreshDriver", "Tick"); - - mMostRecentRefresh = aNowTime; - mMostRecentRefreshEpochTime = aNowEpoch; + UpdateMostRecentRefresh(); nsCOMPtr presShell = mPresContext->GetPresShell(); if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { @@ -743,7 +328,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) // wait until we get a Notify() call when we have no observers // before stopping the timer. StopTimer(); - return; + return NS_OK; } /* @@ -756,11 +341,11 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) ObserverArray::EndLimitedIterator etor(mObservers[i]); while (etor.HasMore()) { nsRefPtr obs = etor.GetNext(); - obs->WillRefresh(aNowTime); + obs->WillRefresh(mMostRecentRefresh); if (!mPresContext || !mPresContext->GetPresShell()) { StopTimer(); - return; + return NS_OK; } } @@ -775,7 +360,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) // readded as needed. mFrameRequestCallbackDocs.Clear(); - int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; + int64_t eventTime = mMostRecentRefreshEpochTime / PR_USEC_PER_MSEC; for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) { nsAutoMicroTask mt; frameRequestCallbacks[i]->Sample(eventTime); @@ -827,9 +412,10 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) * for refresh events. */ - ImageRequestParameters parms = {aNowTime}; + ImageRequestParameters parms = {mMostRecentRefresh}; if (mRequests.Count()) { mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms); + EnsureTimerStarted(false); } for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { @@ -847,6 +433,25 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) 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 @@ -895,9 +500,10 @@ nsRefreshDriver::SetThrottled(bool aThrottled) { if (aThrottled != mThrottled) { mThrottled = aThrottled; - if (mActiveTimer) { + if (mTimer) { // We want to switch our timer type here, so just stop and // restart the timer. + StopTimer(); EnsureTimerStarted(true); } } @@ -907,14 +513,14 @@ void nsRefreshDriver::DoRefresh() { // Don't do a refresh unless we're in a state where we should be refreshing. - if (!mFrozen && mPresContext && mActiveTimer) { - DoTick(); + if (!mFrozen && mPresContext && mTimer) { + Notify(nullptr); } } #ifdef DEBUG bool -nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver, +nsRefreshDriver::IsRefreshObserver(nsARefreshObserver *aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); @@ -938,8 +544,8 @@ nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) mFrameRequestCallbackDocs.NoIndex, "Don't schedule the same document multiple times"); mFrameRequestCallbackDocs.AppendElement(aDocument); - - // make sure that the timer is running + // No need to worry about restarting our timer in precise mode if it's + // already running; that will happen automatically when it fires. EnsureTimerStarted(false); } diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index 5f5af4d6478..0347253bd86 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -14,6 +13,7 @@ #include "mozilla/TimeStamp.h" #include "mozFlushType.h" +#include "nsITimer.h" #include "nsCOMPtr.h" #include "nsTObserverArray.h" #include "nsTArray.h" @@ -32,10 +32,6 @@ 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 @@ -50,28 +46,25 @@ public: virtual void WillRefresh(mozilla::TimeStamp aTime) = 0; }; -class nsRefreshDriver MOZ_FINAL : public nsISupports { +class nsRefreshDriver MOZ_FINAL : public nsITimerCallback { 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 @@ -233,8 +226,6 @@ private: typedef nsTObserverArray ObserverArray; typedef nsTHashtable RequestTable; - void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); - void EnsureTimerStarted(bool aAdjustingTimer); void StopTimer(); @@ -242,20 +233,22 @@ 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(); - double GetRefreshTimerInterval() const; - double GetRegularTimerInterval() const; - double GetThrottledTimerInterval() const; + int32_t GetRefreshTimerInterval() const; + int32_t GetRefreshTimerType() const; bool HaveFrameRequestCallbacks() const { return mFrameRequestCallbackDocs.Length() != 0; } - mozilla::RefreshDriverTimer* ChooseTimer() const; - mozilla::RefreshDriverTimer *mActiveTimer; + nsCOMPtr mTimer; + mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null + int64_t mMostRecentRefreshEpochTime; // same thing as mMostRecentRefresh, + // but in microseconds since the epoch. nsPresContext *mPresContext; // weak; pres context passed in constructor // and unset in Disconnect @@ -263,11 +256,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]; @@ -279,14 +272,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 25c498e9e32..497f400237b 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -361,6 +361,4 @@ nsLayoutStatics::Shutdown() nsDOMMutationObserver::Shutdown(); ContentParent::ShutDown(); - - nsRefreshDriver::Shutdown(); } diff --git a/xpcom/ds/TimeStamp.h b/xpcom/ds/TimeStamp.h index d80cd059b24..401ef2ce830 100644 --- a/xpcom/ds/TimeStamp.h +++ b/xpcom/ds/TimeStamp.h @@ -83,18 +83,6 @@ 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; }