зеркало из https://github.com/mozilla/gecko-dev.git
b=731974, requestAnimationFrame generates too short/long frames; r=bz,smaug,roc
This commit is contained in:
Родитель
45d3492215
Коммит
1712c24093
|
@ -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 <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"
|
||||
|
@ -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<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 */ 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)
|
||||
: 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)
|
||||
{
|
||||
// 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,26 +535,18 @@ 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<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
|
||||
|
||||
return mMostRecentRefresh;
|
||||
}
|
||||
|
||||
int64_t
|
||||
nsRefreshDriver::MostRecentRefreshEpochTime() const
|
||||
{
|
||||
const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(false);
|
||||
|
||||
return mMostRecentRefreshEpochTime;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
mActiveTimer->RemoveRefreshDriver(this);
|
||||
mActiveTimer = nullptr;
|
||||
|
||||
if (mRequestedHighPrecision) {
|
||||
SetHighPrecisionTimersEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
mTimer->Cancel();
|
||||
mTimer = nullptr;
|
||||
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<nsIPresShell> 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<nsARefreshObserver> 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,8 +907,8 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<nsARefreshObserver*> ObserverArray;
|
||||
typedef nsTHashtable<nsISupportsHashKey> 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<nsITimer> 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<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_) */
|
||||
|
|
|
@ -359,4 +359,6 @@ nsLayoutStatics::Shutdown()
|
|||
nsDOMMutationObserver::Shutdown();
|
||||
|
||||
ContentParent::ShutDown();
|
||||
|
||||
nsRefreshDriver::Shutdown();
|
||||
}
|
||||
|
|
|
@ -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<double>(mValue) / aOther.mValue;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче