b=731974, requestAnimationFrame generates too short/long frames; r=bz,smaug,roc

This commit is contained in:
Vladimir Vukicevic 2012-10-02 10:54:46 -04:00
Родитель 45d3492215
Коммит 1712c24093
4 изменённых файлов: 560 добавлений и 145 удалений

Просмотреть файл

@ -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;
}