| author | Ed Morley <emorley@mozilla.com> |
| Mon, 08 Oct 2012 10:57:12 +0100 | |
| changeset 109633 | 9738e5a0190a87202f089315897bf252a99df18f |
| parent 109632 | 08bf91d3c0c33a07707b591cda32fa58675b0e1e |
| child 109652 | 90c035e4ffd9567240b0b0d56a60757aa3b34740 |
| child 109665 | 8b78045e153624f66ace247eef2723e742efe86e |
| push id | 23640 |
| push user | emorley@mozilla.com |
| push date | Mon, 08 Oct 2012 09:59:07 +0000 |
| treeherder | mozilla-central@9738e5a0190a [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| bugs | 731974 |
| milestone | 18.0a1 |
| backs out | 739aff49b8bb7ab8971c874c8212f5d62a8bcd45 |
| first release with | nightly linux32
9738e5a0190a
/
18.0a1
/
20121008031745
/
files
nightly linux64
9738e5a0190a
/
18.0a1
/
20121008031745
/
files
nightly mac
9738e5a0190a
/
18.0a1
/
20121008031745
/
files
nightly win32
9738e5a0190a
/
18.0a1
/
20121008031745
/
files
nightly win64
9738e5a0190a
/
18.0a1
/
20121008031745
/
files
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| releases | nightly linux32
18.0a1
/
20121008031745
/
pushlog to previous
nightly linux64
18.0a1
/
20121008031745
/
pushlog to previous
nightly mac
18.0a1
/
20121008031745
/
pushlog to previous
nightly win32
18.0a1
/
20121008031745
/
pushlog to previous
nightly win64
18.0a1
/
20121008031745
/
pushlog to previous
|
--- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -2,37 +2,21 @@ /* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * 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 <windows.h> -// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have -// to manually include it -#include <mmsystem.h> -#endif - #include "mozilla/Util.h" #include "nsRefreshDriver.h" -#include "nsITimer.h" #include "nsPresContext.h" #include "nsComponentManagerUtils.h" #include "prlog.h" #include "nsAutoPtr.h" #include "nsCSSFrameConstructor.h" #include "nsIDocument.h" #include "nsGUIEvent.h" #include "nsEventDispatcher.h" @@ -42,533 +26,148 @@ #include "nsIViewManager.h" #include "sampler.h" using mozilla::TimeStamp; 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<nsRefPtr<nsRefreshDriver> > 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<nsRefPtr<nsRefreshDriver> > 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<RefreshDriverTimer*>(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<nsITimer> 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<int>((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<uint32_t>((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<uint32_t>(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<nsRefPtr<nsRefreshDriver> > 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<InactiveRefreshDriverTimer*>(aClosure); - timer->TickOne(); - } - - nsRefPtr<nsITimer> 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 nsRefreshDriver::DefaultInterval() { return NSToIntRound(1000.0 / DEFAULT_FRAME_RATE); } // Compute the interval to use for the refresh driver timer, in // milliseconds -double -nsRefreshDriver::GetRegularTimerInterval() const +int32_t +nsRefreshDriver::GetRefreshTimerInterval() const { - int32_t rate = Preferences::GetInt("layout.frame_rate", -1); + 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 = DEFAULT_FRAME_RATE; + rate = mThrottled ? DEFAULT_THROTTLED_FRAME_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; + NS_ASSERTION(rate > 0, "Must have positive rate here"); + int32_t interval = NSToIntRound(1000.0/rate); + if (mThrottled) { + interval = NS_MAX(interval, mLastTimerInterval * 2); } - return 1000.0 / rate; + mLastTimerInterval = interval; + return interval; } -double -nsRefreshDriver::GetRefreshTimerInterval() const -{ - return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval(); -} - -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(); } 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(); } mPresShellsToInvalidateIfHidden.Clear(); } // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh // for description. 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(); } } 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<nsRefreshDriver*>(this)->EnsureTimerStarted(false); + return mMostRecentRefresh; } int64_t nsRefreshDriver::MostRecentRefreshEpochTime() const { + const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false); + return mMostRecentRefreshEpochTime; } bool -nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, +nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); bool success = array.AppendElement(aObserver) != nullptr; EnsureTimerStarted(false); return success; } bool -nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, +nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver, mozFlushType aFlushType) { ObserverArray& array = ArrayFor(aFlushType); return array.RemoveElement(aObserver); } bool nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) @@ -591,77 +190,57 @@ nsRefreshDriver::RemoveImageRequest(imgI void nsRefreshDriver::ClearAllImageRequests() { mRequests.Clear(); } 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 nsRefreshDriver::ObserverCount() const { uint32_t sum = 0; for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { sum += mObservers[i].Length(); @@ -679,16 +258,28 @@ nsRefreshDriver::ObserverCount() const } uint32_t 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) { switch (aFlushType) { case Flush_Style: return mObservers[0]; case Flush_Layout: return mObservers[1]; @@ -699,88 +290,82 @@ nsRefreshDriver::ArrayFor(mozFlushType a return *static_cast<ObserverArray*>(nullptr); } } /* * 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<nsIPresShell> presShell = mPresContext->GetPresShell(); if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { // Things are being destroyed, or we no longer have any observers. // We don't want to stop the timer when observers are initially // removed, because sometimes observers can be added and removed // often depending on what other things are going on and in that // situation we don't want to thrash our timer. So instead we // wait until we get a Notify() call when we have no observers // before stopping the timer. StopTimer(); - return; + return NS_OK; } /* * The timer holds a reference to |this| while calling |Notify|. * However, implementations of |WillRefresh| are permitted to destroy * the pres context, which will cause our |mPresContext| to become * null. If this happens, we must stop notifying observers. */ for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { ObserverArray::EndLimitedIterator etor(mObservers[i]); while (etor.HasMore()) { nsRefPtr<nsARefreshObserver> obs = etor.GetNext(); - obs->WillRefresh(aNowTime); + obs->WillRefresh(mMostRecentRefresh); if (!mPresContext || !mPresContext->GetPresShell()) { StopTimer(); - return; + return NS_OK; } } if (i == 0) { // Grab all of our frame request callbacks up front. nsIDocument::FrameRequestCallbackList frameRequestCallbacks; for (uint32_t i = 0; i < mFrameRequestCallbackDocs.Length(); ++i) { mFrameRequestCallbackDocs[i]-> TakeFrameRequestCallbacks(frameRequestCallbacks); } // OK, now reset mFrameRequestCallbackDocs so they can be // 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); } // This is the Flush_Style case. if (mPresContext && mPresContext->GetPresShell()) { nsAutoTArray<nsIPresShell*, 16> observers; @@ -822,19 +407,20 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, } } /* * Perform notification to imgIRequests subscribed to listen * 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++) { mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); } mPresShellsToInvalidateIfHidden.Clear(); if (mViewManagerFlushIsPending) { @@ -842,16 +428,35 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, printf("Starting ProcessPendingUpdates\n"); #endif mViewManagerFlushIsPending = false; mPresContext->GetPresShell()->GetViewManager()->ProcessPendingUpdates(); #ifdef DEBUG_INVALIDATIONS 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 nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry, void* aUserArg) { ImageRequestParameters* parms = static_cast<ImageRequestParameters*> (aUserArg); @@ -890,36 +495,37 @@ nsRefreshDriver::Thaw() } } void 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); } } } 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); return array.Contains(aObserver); } #endif void @@ -933,18 +539,18 @@ nsRefreshDriver::ScheduleViewManagerFlus void nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) { NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(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); } void nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument) { mFrameRequestCallbackDocs.RemoveElement(aDocument); // No need to worry about restarting our timer in slack mode if it's already
--- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -1,24 +1,24 @@ -/* -*- 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Code to notify things that animate before a refresh, at an appropriate * refresh rate. (Perhaps temporary, until replaced by compositor.) */ #ifndef nsRefreshDriver_h_ #define nsRefreshDriver_h_ #include "mozilla/TimeStamp.h" #include "mozFlushType.h" +#include "nsITimer.h" #include "nsCOMPtr.h" #include "nsTObserverArray.h" #include "nsTArray.h" #include "nsAutoPtr.h" #include "nsTHashtable.h" #include "nsHashKeys.h" #include "mozilla/Attributes.h" @@ -27,56 +27,49 @@ class nsIPresShell; class nsIDocument; class imgIRequest; /** * An abstract base class to be implemented by callers wanting to be * 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 // must implement reference counting, and those that do implement // nsISupports will already have methods with the correct signature. // // The refresh driver does NOT hold references to refresh observers // except while it is notifying them. NS_IMETHOD_(nsrefcnt) AddRef(void) = 0; NS_IMETHOD_(nsrefcnt) Release(void) = 0; 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 * used by callers who want to start an animation now and want to know * what time to consider the start of the animation. (This helps * ensure that multiple animations started during the same event off * the main event loop have the same start time.) */ @@ -228,65 +221,65 @@ public: * Default interval the refresh driver uses, in ms. */ static int32_t DefaultInterval(); private: typedef nsTObserverArray<nsARefreshObserver*> ObserverArray; typedef nsTHashtable<nsISupportsHashKey> RequestTable; - void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); - void EnsureTimerStarted(bool aAdjustingTimer); void StopTimer(); uint32_t ObserverCount() const; 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<nsITimer> 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 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]; RequestTable mRequests; nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers; nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers; nsAutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden; // nsTArray on purpose, because we want to be able to swap. nsTArray<nsIDocument*> 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_) */
--- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -356,11 +356,9 @@ nsLayoutStatics::Shutdown() nsLayoutUtils::Shutdown(); nsHyphenationManager::Shutdown(); nsEditorSpellCheck::ShutDown(); nsDOMMutationObserver::Shutdown(); ContentParent::ShutDown(); - - nsRefreshDriver::Shutdown(); }
--- a/xpcom/ds/TimeStamp.h +++ b/xpcom/ds/TimeStamp.h @@ -78,28 +78,16 @@ public: TimeDuration& operator+=(const TimeDuration& aOther) { mValue += aOther.mValue; return *this; } TimeDuration& operator-=(const TimeDuration& aOther) { 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<double>(mValue) / aOther.mValue; } bool operator<(const TimeDuration& aOther) const { return mValue < aOther.mValue; } bool operator<=(const TimeDuration& aOther) const {