gecko/layout/base/nsRefreshDriver.cpp
Robert O'Callahan be82b38545 Bug 829557. Part 1: When calling into plugin code, identify situations where it is safe (or unsafe) to reenter Gecko from plugin code. r=bsmedberg
When Gecko is reentered from unsafe call sites, we allow the reentry, but we
suppress execution of the refresh driver to minimize the danger.
In this patch, we treat all sites as unsafe.

--HG--
rename : toolkit/modules/Timer.jsm => browser/devtools/shared/Browser.jsm
rename : toolkit/modules/tests/xpcshell/test_timer.js => browser/devtools/shared/test/browser_browser_basic.js
rename : build/mach_bootstrap.py => mach
extra : rebase_source : b83c1d09313bff62357eaa931eced0f72f838493
2013-02-28 00:50:27 +13:00

1069 lines
31 KiB
C++

/* -*- 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.)
*
* 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"
#include "jsapi.h"
#include "nsContentUtils.h"
#include "mozilla/Preferences.h"
#include "nsViewManager.h"
#include "sampler.h"
#include "nsNPAPIPluginInstance.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)
{
LOG("[%p] AddRefreshDriver %p", this, 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)
{
LOG("[%p] RemoveRefreshDriver %p", this, 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;
LOG("[%p] ticking drivers...", this);
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);
}
LOG("[%p] done.", this);
}
static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
{
LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow);
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()
{
// pretend we just fired, and we schedule the next tick normally
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
mTargetTime = mLastFireTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
mTimer->InitWithFuncCallback(TimerTick, this, delay, 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);
if (numElapsedIntervals < 0) {
// It's possible that numElapsedIntervals is negative (e.g. timer compensation
// may result in (aNowTime - mTargetTime) < -1.0/mRateDuration, which will result in
// negative numElapsedIntervals), so make sure we don't target the same timestamp.
numElapsedIntervals = 0;
}
// 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
LOG("[%p] scheduling callback for %d ms (2)", this, delay);
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()
{
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
mTargetTime = mLastFireTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
mTimer->InitWithFuncCallback(TimerTickOne, this, delay, 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;
#ifdef XP_WIN
static int32_t sHighPrecisionTimerRequests = 0;
// a bare pointer to avoid introducing a static constructor
static nsITimer *sDisableHighPrecisionTimersTimer = nullptr;
#endif
/* 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;
#ifdef XP_WIN
if (sDisableHighPrecisionTimersTimer) {
sDisableHighPrecisionTimersTimer->Cancel();
NS_RELEASE(sDisableHighPrecisionTimersTimer);
timeEndPeriod(1);
} else if (sHighPrecisionTimerRequests) {
timeEndPeriod(1);
}
#endif
}
/* 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 rate = Preferences::GetInt("layout.frame_rate", -1);
if (rate <= 0) {
// TODO: get the rate from the platform
rate = DEFAULT_FRAME_RATE;
}
return 1000.0 / rate;
}
double
nsRefreshDriver::GetThrottledTimerInterval() const
{
int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
if (rate <= 0) {
rate = DEFAULT_THROTTLED_FRAME_RATE;
}
return 1000.0 / rate;
}
double
nsRefreshDriver::GetRefreshTimerInterval() const
{
return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval();
}
RefreshDriverTimer*
nsRefreshDriver::ChooseTimer() const
{
if (mThrottled) {
if (!sThrottledRateTimer)
sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(),
DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
return sThrottledRateTimer;
}
if (!sRegularRateTimer)
sRegularRateTimer = new PreciseRefreshDriverTimer(GetRegularTimerInterval());
return sRegularRateTimer;
}
nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
: mActiveTimer(nullptr),
mPresContext(aPresContext),
mFrozen(false),
mThrottled(false),
mTestControllingRefreshes(false),
mViewManagerFlushIsPending(false),
mRequestedHighPrecision(false)
{
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");
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;
}
mMostRecentRefreshEpochTime += aMilliseconds * 1000;
mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds);
nsCxPusher pusher;
pusher.PushNull();
DoTick();
}
void
nsRefreshDriver::RestoreNormalRefresh()
{
mTestControllingRefreshes = false;
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;
}
bool
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,
mozFlushType aFlushType)
{
ObserverArray& array = ArrayFor(aFlushType);
return array.RemoveElement(aObserver);
}
bool
nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
{
if (!mRequests.PutEntry(aRequest)) {
return false;
}
EnsureTimerStarted(false);
return true;
}
void
nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
{
mRequests.RemoveEntry(aRequest);
}
void nsRefreshDriver::ClearAllImageRequests()
{
mRequests.Clear();
}
void
nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
{
if (mTestControllingRefreshes)
return;
// 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;
}
// We got here because we're either adjusting the time *or* we're
// starting it for the first time. Add to the right timer,
// prehaps removing it from a previously-set one.
RefreshDriverTimer *newTimer = ChooseTimer();
if (newTimer != mActiveTimer) {
if (mActiveTimer)
mActiveTimer->RemoveRefreshDriver(this);
mActiveTimer = newTimer;
mActiveTimer->AddRefreshDriver(this);
}
mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
mMostRecentRefreshEpochTime = mActiveTimer->MostRecentRefreshEpochTime();
}
void
nsRefreshDriver::StopTimer()
{
if (!mActiveTimer)
return;
mActiveTimer->RemoveRefreshDriver(this);
mActiveTimer = nullptr;
if (mRequestedHighPrecision) {
SetHighPrecisionTimersEnabled(false);
}
}
#ifdef XP_WIN
static void
DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure)
{
timeEndPeriod(1);
NS_RELEASE(sDisableHighPrecisionTimersTimer);
}
#endif
void
nsRefreshDriver::ConfigureHighPrecision()
{
bool haveFrameRequestCallbacks = mFrameRequestCallbackDocs.Length() > 0;
// if the only change that's needed is that we need high precision,
// then just set that
if (!mThrottled && !mRequestedHighPrecision && haveFrameRequestCallbacks) {
SetHighPrecisionTimersEnabled(true);
} else if (mRequestedHighPrecision && !haveFrameRequestCallbacks) {
SetHighPrecisionTimersEnabled(false);
}
}
void
nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable)
{
LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false");
if (aEnable) {
NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!");
#ifdef XP_WIN
if (++sHighPrecisionTimerRequests == 1) {
// If we had a timer scheduled to disable it, that means that it's already
// enabled; just cancel the timer. Otherwise, really enable it.
if (sDisableHighPrecisionTimersTimer) {
sDisableHighPrecisionTimersTimer->Cancel();
NS_RELEASE(sDisableHighPrecisionTimersTimer);
} else {
timeBeginPeriod(1);
}
}
#endif
mRequestedHighPrecision = true;
} else {
NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!");
#ifdef XP_WIN
if (--sHighPrecisionTimerRequests == 0) {
// Don't jerk us around between high precision and low precision
// timers; instead, only allow leaving high precision timers
// after 90 seconds. This is arbitrary, but hopefully good
// enough.
NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !");
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (timer) {
timer.forget(&sDisableHighPrecisionTimersTimer);
sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback,
nullptr,
90 * 1000,
nsITimer::TYPE_ONE_SHOT);
} else {
// might happen if we're shutting down XPCOM; just drop the time period down
// immediately
timeEndPeriod(1);
}
}
#endif
mRequestedHighPrecision = false;
}
}
uint32_t
nsRefreshDriver::ObserverCount() const
{
uint32_t sum = 0;
for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
sum += mObservers[i].Length();
}
// Even while throttled, we need to process layout and style changes. Style
// changes can trigger transitions which fire events when they complete, and
// layout changes can affect media queries on child documents, triggering
// style changes, etc.
sum += mStyleFlushObservers.Length();
sum += mLayoutFlushObservers.Length();
sum += mFrameRequestCallbackDocs.Length();
sum += mViewManagerFlushIsPending;
return sum;
}
uint32_t
nsRefreshDriver::ImageRequestCount() const
{
return mRequests.Count();
}
nsRefreshDriver::ObserverArray&
nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
{
switch (aFlushType) {
case Flush_Style:
return mObservers[0];
case Flush_Layout:
return mObservers[1];
case Flush_Display:
return mObservers[2];
default:
NS_ABORT_IF_FALSE(false, "bad flush type");
return *static_cast<ObserverArray*>(nullptr);
}
}
/*
* nsISupports implementation
*/
NS_IMPL_ISUPPORTS1(nsRefreshDriver, nsISupports)
/*
* nsITimerCallback implementation
*/
void
nsRefreshDriver::DoTick()
{
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());
}
}
void
nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
{
NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
"Shouldn't have a JSContext on the stack");
if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) {
NS_ERROR("Refresh driver should not run during plugin call!");
// Try to survive this by just ignoring the refresh tick.
return;
}
SAMPLE_LABEL("nsRefreshDriver", "Tick");
// We're either frozen or we were disconnected (likely in the middle
// of a tick iteration). Just do nothing here, since our
// prescontext went away.
if (mFrozen || !mPresContext) {
return;
}
mMostRecentRefresh = aNowTime;
mMostRecentRefreshEpochTime = aNowEpoch;
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;
}
/*
* 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);
if (!mPresContext || !mPresContext->GetPresShell()) {
StopTimer();
return;
}
}
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;
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;
observers.AppendElements(mStyleFlushObservers);
for (uint32_t j = observers.Length();
j && mPresContext && mPresContext->GetPresShell(); --j) {
// Make sure to not process observers which might have been removed
// during previous iterations.
nsIPresShell* shell = observers[j - 1];
if (!mStyleFlushObservers.Contains(shell))
continue;
NS_ADDREF(shell);
mStyleFlushObservers.RemoveElement(shell);
shell->FrameConstructor()->mObservingRefreshDriver = false;
shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
NS_RELEASE(shell);
}
}
} else if (i == 1) {
// This is the Flush_Layout case.
if (mPresContext && mPresContext->GetPresShell()) {
nsAutoTArray<nsIPresShell*, 16> observers;
observers.AppendElements(mLayoutFlushObservers);
for (uint32_t j = observers.Length();
j && mPresContext && mPresContext->GetPresShell(); --j) {
// Make sure to not process observers which might have been removed
// during previous iterations.
nsIPresShell* shell = observers[j - 1];
if (!mLayoutFlushObservers.Contains(shell))
continue;
NS_ADDREF(shell);
mLayoutFlushObservers.RemoveElement(shell);
shell->mReflowScheduled = false;
shell->mSuppressInterruptibleReflows = false;
shell->FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout,
false));
NS_RELEASE(shell);
}
}
}
}
/*
* Perform notification to imgIRequests subscribed to listen
* for refresh events.
*/
ImageRequestParameters parms = {aNowTime};
if (mRequests.Count()) {
mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms);
}
for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) {
mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden();
}
mPresShellsToInvalidateIfHidden.Clear();
if (mViewManagerFlushIsPending) {
#ifdef DEBUG_INVALIDATIONS
printf("Starting ProcessPendingUpdates\n");
#endif
#ifndef MOZ_WIDGET_GONK
// Waiting for bug 830475 to work on B2G.
nsRefPtr<layers::LayerManager> mgr = mPresContext->GetPresShell()->GetLayerManager();
if (mgr) {
mgr->SetPaintStartTime(mMostRecentRefresh);
}
#endif
mViewManagerFlushIsPending = false;
nsRefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
vm->ProcessPendingUpdates();
#ifdef DEBUG_INVALIDATIONS
printf("Ending ProcessPendingUpdates\n");
#endif
}
}
PLDHashOperator
nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
void* aUserArg)
{
ImageRequestParameters* parms =
static_cast<ImageRequestParameters*> (aUserArg);
mozilla::TimeStamp mostRecentRefresh = parms->ts;
imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
nsCOMPtr<imgIContainer> image;
if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
image->RequestRefresh(mostRecentRefresh);
}
return PL_DHASH_NEXT;
}
void
nsRefreshDriver::Freeze()
{
NS_ASSERTION(!mFrozen, "Freeze called on already-frozen refresh driver");
StopTimer();
mFrozen = true;
}
void
nsRefreshDriver::Thaw()
{
NS_ASSERTION(mFrozen, "Thaw called on an unfrozen refresh driver");
mFrozen = false;
if (ObserverCount() || ImageRequestCount()) {
// FIXME: This isn't quite right, since our EnsureTimerStarted call
// updates our mMostRecentRefresh, but the DoRefresh call won't run
// and notify our observers until we get back to the event loop.
// Thus MostRecentRefresh() will lie between now and the DoRefresh.
NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
EnsureTimerStarted(false);
}
}
void
nsRefreshDriver::SetThrottled(bool aThrottled)
{
if (aThrottled != mThrottled) {
mThrottled = aThrottled;
if (mActiveTimer) {
// We want to switch our timer type here, so just stop and
// restart the timer.
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();
}
}
#ifdef DEBUG
bool
nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
mozFlushType aFlushType)
{
ObserverArray& array = ArrayFor(aFlushType);
return array.Contains(aObserver);
}
#endif
void
nsRefreshDriver::ScheduleViewManagerFlush()
{
NS_ASSERTION(mPresContext->IsRoot(),
"Should only schedule view manager flush on root prescontexts");
mViewManagerFlushIsPending = true;
EnsureTimerStarted(false);
}
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
ConfigureHighPrecision();
EnsureTimerStarted(false);
}
void
nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
{
mFrameRequestCallbackDocs.RemoveElement(aDocument);
ConfigureHighPrecision();
// No need to worry about restarting our timer in slack mode if it's already
// running; that will happen automatically when it fires.
}