michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Code to notify things that animate before a refresh, at an appropriate michael@0: * refresh rate. (Perhaps temporary, until replaced by compositor.) michael@0: * michael@0: * Chrome and each tab have their own RefreshDriver, which in turn michael@0: * hooks into one of a few global timer based on RefreshDriverTimer, michael@0: * defined below. There are two main global timers -- one for active michael@0: * animations, and one for inactive ones. These are implemented as michael@0: * subclasses of RefreshDriverTimer; see below for a description of michael@0: * their implementations. In the future, additional timer types may michael@0: * implement things like blocking on vsync. michael@0: */ michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: // mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have michael@0: // to manually include it michael@0: #include michael@0: #include "WinUtils.h" michael@0: #endif michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "nsRefreshDriver.h" michael@0: #include "nsITimer.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "prlog.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIDocument.h" michael@0: #include "jsapi.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsViewManager.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsNPAPIPluginInstance.h" michael@0: #include "nsPerformance.h" michael@0: #include "mozilla/dom/WindowBinding.h" michael@0: #include "RestyleManager.h" michael@0: #include "Layers.h" michael@0: #include "imgIContainer.h" michael@0: #include "nsIFrameRequestCallback.h" michael@0: #include "mozilla/dom/ScriptSettings.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo *gLog = nullptr; michael@0: #define LOG(...) PR_LOG(gLog, PR_LOG_NOTICE, (__VA_ARGS__)) michael@0: #else michael@0: #define LOG(...) do { } while(0) michael@0: #endif michael@0: michael@0: #define DEFAULT_FRAME_RATE 60 michael@0: #define DEFAULT_THROTTLED_FRAME_RATE 1 michael@0: // after 10 minutes, stop firing off inactive timers michael@0: #define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600 michael@0: michael@0: namespace mozilla { michael@0: michael@0: /* michael@0: * The base class for all global refresh driver timers. It takes care michael@0: * of managing the list of refresh drivers attached to them and michael@0: * provides interfaces for querying/setting the rate and actually michael@0: * running a timer 'Tick'. Subclasses must implement StartTimer(), michael@0: * StopTimer(), and ScheduleNextTick() -- the first two just michael@0: * start/stop whatever timer mechanism is in use, and ScheduleNextTick michael@0: * is called at the start of the Tick() implementation to set a time michael@0: * for the next tick. michael@0: */ michael@0: class RefreshDriverTimer { michael@0: public: michael@0: /* michael@0: * aRate -- the delay, in milliseconds, requested between timer firings michael@0: */ michael@0: RefreshDriverTimer(double aRate) michael@0: { michael@0: SetRate(aRate); michael@0: } michael@0: michael@0: virtual ~RefreshDriverTimer() michael@0: { michael@0: NS_ASSERTION(mRefreshDrivers.Length() == 0, "Should have removed all refresh drivers from here by now!"); michael@0: } michael@0: michael@0: virtual void AddRefreshDriver(nsRefreshDriver* aDriver) michael@0: { michael@0: LOG("[%p] AddRefreshDriver %p", this, aDriver); michael@0: michael@0: NS_ASSERTION(!mRefreshDrivers.Contains(aDriver), "AddRefreshDriver for a refresh driver that's already in the list!"); michael@0: mRefreshDrivers.AppendElement(aDriver); michael@0: michael@0: if (mRefreshDrivers.Length() == 1) { michael@0: StartTimer(); michael@0: } michael@0: } michael@0: michael@0: virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver) michael@0: { michael@0: LOG("[%p] RemoveRefreshDriver %p", this, aDriver); michael@0: michael@0: NS_ASSERTION(mRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the list!"); michael@0: mRefreshDrivers.RemoveElement(aDriver); michael@0: michael@0: if (mRefreshDrivers.Length() == 0) { michael@0: StopTimer(); michael@0: } michael@0: } michael@0: michael@0: double GetRate() const michael@0: { michael@0: return mRateMilliseconds; michael@0: } michael@0: michael@0: // will take effect at next timer tick michael@0: virtual void SetRate(double aNewRate) michael@0: { michael@0: mRateMilliseconds = aNewRate; michael@0: mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds); michael@0: } michael@0: michael@0: TimeStamp MostRecentRefresh() const { return mLastFireTime; } michael@0: int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; } michael@0: michael@0: protected: michael@0: virtual void StartTimer() = 0; michael@0: virtual void StopTimer() = 0; michael@0: virtual void ScheduleNextTick(TimeStamp aNowTime) = 0; michael@0: michael@0: /* michael@0: * Actually runs a tick, poking all the attached RefreshDrivers. michael@0: * Grabs the "now" time via JS_Now and TimeStamp::Now(). michael@0: */ michael@0: void Tick() michael@0: { michael@0: int64_t jsnow = JS_Now(); michael@0: TimeStamp now = TimeStamp::Now(); michael@0: michael@0: ScheduleNextTick(now); michael@0: michael@0: mLastFireEpoch = jsnow; michael@0: mLastFireTime = now; michael@0: michael@0: LOG("[%p] ticking drivers...", this); michael@0: nsTArray > drivers(mRefreshDrivers); michael@0: for (size_t i = 0; i < drivers.Length(); ++i) { michael@0: // don't poke this driver if it's in test mode michael@0: if (drivers[i]->IsTestControllingRefreshesEnabled()) { michael@0: continue; michael@0: } michael@0: michael@0: TickDriver(drivers[i], jsnow, now); michael@0: } michael@0: LOG("[%p] done.", this); michael@0: } michael@0: michael@0: static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now) michael@0: { michael@0: LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow); michael@0: driver->Tick(jsnow, now); michael@0: } michael@0: michael@0: double mRateMilliseconds; michael@0: TimeDuration mRateDuration; michael@0: michael@0: int64_t mLastFireEpoch; michael@0: TimeStamp mLastFireTime; michael@0: TimeStamp mTargetTime; michael@0: michael@0: nsTArray > mRefreshDrivers; michael@0: michael@0: // useful callback for nsITimer-based derived classes, here michael@0: // bacause of c++ protected shenanigans michael@0: static void TimerTick(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: RefreshDriverTimer *timer = static_cast(aClosure); michael@0: timer->Tick(); michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that michael@0: * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to michael@0: * implement ScheduleNextTick and intelligently calculate the next time to tick, michael@0: * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain michael@0: * with its attempt at intelligent slack removal and such, so we don't do it. michael@0: */ michael@0: class SimpleTimerBasedRefreshDriverTimer : michael@0: public RefreshDriverTimer michael@0: { michael@0: public: michael@0: SimpleTimerBasedRefreshDriverTimer(double aRate) michael@0: : RefreshDriverTimer(aRate) michael@0: { michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: } michael@0: michael@0: virtual ~SimpleTimerBasedRefreshDriverTimer() michael@0: { michael@0: StopTimer(); michael@0: } michael@0: michael@0: protected: michael@0: michael@0: virtual void StartTimer() michael@0: { michael@0: // pretend we just fired, and we schedule the next tick normally michael@0: mLastFireEpoch = JS_Now(); michael@0: mLastFireTime = TimeStamp::Now(); michael@0: michael@0: mTargetTime = mLastFireTime + mRateDuration; michael@0: michael@0: uint32_t delay = static_cast(mRateMilliseconds); michael@0: mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: virtual void StopTimer() michael@0: { michael@0: mTimer->Cancel(); michael@0: } michael@0: michael@0: nsRefPtr mTimer; michael@0: }; michael@0: michael@0: /* michael@0: * PreciseRefreshDriverTimer schedules ticks based on the current time michael@0: * and when the next tick -should- be sent if we were hitting our michael@0: * rate. It always schedules ticks on multiples of aRate -- meaning that michael@0: * if some execution takes longer than an alloted slot, the next tick michael@0: * will be delayed instead of triggering instantly. This might not be michael@0: * desired -- there's an #if 0'd block below that we could put behind michael@0: * a pref to control this behaviour. michael@0: */ michael@0: class PreciseRefreshDriverTimer : michael@0: public SimpleTimerBasedRefreshDriverTimer michael@0: { michael@0: public: michael@0: PreciseRefreshDriverTimer(double aRate) michael@0: : SimpleTimerBasedRefreshDriverTimer(aRate) michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: virtual void ScheduleNextTick(TimeStamp aNowTime) michael@0: { michael@0: // The number of (whole) elapsed intervals between the last target michael@0: // time and the actual time. We want to truncate the double down michael@0: // to an int number of intervals. michael@0: int numElapsedIntervals = static_cast((aNowTime - mTargetTime) / mRateDuration); michael@0: michael@0: if (numElapsedIntervals < 0) { michael@0: // It's possible that numElapsedIntervals is negative (e.g. timer compensation michael@0: // may result in (aNowTime - mTargetTime) < -1.0/mRateDuration, which will result in michael@0: // negative numElapsedIntervals), so make sure we don't target the same timestamp. michael@0: numElapsedIntervals = 0; michael@0: } michael@0: michael@0: // the last "tick" that may or may not have been actually sent was michael@0: // at this time. For example, if the rate is 15ms, the target michael@0: // time is 200ms, and it's now 225ms, the last effective tick michael@0: // would have been at 215ms. The next one should then be michael@0: // scheduled for 5 ms from now. michael@0: // michael@0: // We then add another mRateDuration to find the next tick target. michael@0: TimeStamp newTarget = mTargetTime + mRateDuration * (numElapsedIntervals + 1); michael@0: michael@0: // the amount of (integer) ms until the next time we should tick michael@0: uint32_t delay = static_cast((newTarget - aNowTime).ToMilliseconds()); michael@0: michael@0: // Without this block, we'll always schedule on interval ticks; michael@0: // with it, we'll schedule immediately if we missed our tick target michael@0: // last time. michael@0: #if 0 michael@0: if (numElapsedIntervals > 0) { michael@0: // we're late, so reset michael@0: newTarget = aNowTime; michael@0: delay = 0; michael@0: } michael@0: #endif michael@0: michael@0: // log info & lateness michael@0: LOG("[%p] precise timer last tick late by %f ms, next tick in %d ms", michael@0: this, michael@0: (aNowTime - mTargetTime).ToMilliseconds(), michael@0: delay); michael@0: michael@0: // then schedule the timer michael@0: LOG("[%p] scheduling callback for %d ms (2)", this, delay); michael@0: mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); michael@0: michael@0: mTargetTime = newTarget; michael@0: } michael@0: }; michael@0: michael@0: #ifdef XP_WIN michael@0: /* michael@0: * Uses vsync timing on windows with DWM. Falls back dynamically to fixed rate if required. michael@0: */ michael@0: class PreciseRefreshDriverTimerWindowsDwmVsync : michael@0: public PreciseRefreshDriverTimer michael@0: { michael@0: public: michael@0: // Checks if the vsync API is accessible. michael@0: static bool IsSupported() michael@0: { michael@0: return WinUtils::dwmGetCompositionTimingInfoPtr != nullptr; michael@0: } michael@0: michael@0: PreciseRefreshDriverTimerWindowsDwmVsync(double aRate, bool aPreferHwTiming = false) michael@0: : PreciseRefreshDriverTimer(aRate) michael@0: , mPreferHwTiming(aPreferHwTiming) michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: // Indicates we should try to adjust to the HW's timing (get rate from the OS or use vsync) michael@0: // This is typically true if the default refresh-rate value was not modified by the user. michael@0: bool mPreferHwTiming; michael@0: michael@0: nsresult GetVBlankInfo(mozilla::TimeStamp &aLastVBlank, mozilla::TimeDuration &aInterval) michael@0: { michael@0: MOZ_ASSERT(WinUtils::dwmGetCompositionTimingInfoPtr, michael@0: "DwmGetCompositionTimingInfoPtr is unavailable (windows vsync)"); michael@0: michael@0: DWM_TIMING_INFO timingInfo; michael@0: timingInfo.cbSize = sizeof(DWM_TIMING_INFO); michael@0: HRESULT hr = WinUtils::dwmGetCompositionTimingInfoPtr(0, &timingInfo); // For the desktop window instead of a specific one. michael@0: michael@0: if (FAILED(hr)) { michael@0: // This happens first time this is called. michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: LARGE_INTEGER time, freq; michael@0: ::QueryPerformanceCounter(&time); michael@0: ::QueryPerformanceFrequency(&freq); michael@0: aLastVBlank = TimeStamp::Now(); michael@0: double secondsPassed = double(time.QuadPart - timingInfo.qpcVBlank) / double(freq.QuadPart); michael@0: michael@0: aLastVBlank -= TimeDuration::FromSeconds(secondsPassed); michael@0: aInterval = TimeDuration::FromSeconds(double(timingInfo.qpcRefreshPeriod) / double(freq.QuadPart)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: virtual void ScheduleNextTick(TimeStamp aNowTime) michael@0: { michael@0: static const TimeDuration kMinSaneInterval = TimeDuration::FromMilliseconds(3); // 330Hz michael@0: static const TimeDuration kMaxSaneInterval = TimeDuration::FromMilliseconds(44); // 23Hz michael@0: static const TimeDuration kNegativeMaxSaneInterval = TimeDuration::FromMilliseconds(-44); // Saves conversions for abs interval michael@0: TimeStamp lastVblank; michael@0: TimeDuration vblankInterval; michael@0: michael@0: if (!mPreferHwTiming || michael@0: NS_OK != GetVBlankInfo(lastVblank, vblankInterval) || michael@0: vblankInterval > kMaxSaneInterval || michael@0: vblankInterval < kMinSaneInterval || michael@0: (aNowTime - lastVblank) > kMaxSaneInterval || michael@0: (aNowTime - lastVblank) < kNegativeMaxSaneInterval) { michael@0: // Use the default timing without vsync michael@0: PreciseRefreshDriverTimer::ScheduleNextTick(aNowTime); michael@0: return; michael@0: } michael@0: michael@0: TimeStamp newTarget = lastVblank + vblankInterval; // Base target michael@0: michael@0: // However, timer callback might return early (or late, but that wouldn't bother us), and vblankInterval michael@0: // appears to be slightly (~1%) different on each call (probably the OS measuring recent actual interval[s]) michael@0: // and since we don't want to re-target the same vsync, we keep advancing in vblank intervals until we find the michael@0: // next safe target (next vsync, but not within 10% interval of previous target). michael@0: // This is typically 0 or 1 iteration: michael@0: // If we're too early, next vsync would be the one we've already targeted (1 iteration). michael@0: // If the timer returned late, no iteration will be required. michael@0: michael@0: const double kSameVsyncThreshold = 0.1; michael@0: while (newTarget <= mTargetTime + vblankInterval.MultDouble(kSameVsyncThreshold)) { michael@0: newTarget += vblankInterval; michael@0: } michael@0: michael@0: // To make sure we always hit the same "side" of the signal: michael@0: // round the delay up (by adding 1, since we later floor) and add a little (10% by default). michael@0: // Note that newTarget doesn't change (and is the next vblank) as a reference when we're back. michael@0: static const double kDefaultPhaseShiftPercent = 10; michael@0: static const double phaseShiftFactor = 0.01 * michael@0: (Preferences::GetInt("layout.frame_rate.vsync.phasePercentage", kDefaultPhaseShiftPercent) % 100); michael@0: michael@0: double phaseDelay = 1.0 + vblankInterval.ToMilliseconds() * phaseShiftFactor; michael@0: michael@0: // ms until the next time we should tick michael@0: double delayMs = (newTarget - aNowTime).ToMilliseconds() + phaseDelay; michael@0: michael@0: // Make sure the delay is never negative. michael@0: uint32_t delay = static_cast(delayMs < 0 ? 0 : delayMs); michael@0: michael@0: // log info & lateness michael@0: LOG("[%p] precise dwm-vsync timer last tick late by %f ms, next tick in %d ms", michael@0: this, michael@0: (aNowTime - mTargetTime).ToMilliseconds(), michael@0: delay); michael@0: michael@0: // then schedule the timer michael@0: LOG("[%p] scheduling callback for %d ms (2)", this, delay); michael@0: mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); michael@0: michael@0: mTargetTime = newTarget; michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: /* michael@0: * A RefreshDriverTimer for inactive documents. When a new refresh driver is michael@0: * added, the rate is reset to the base (normally 1s/1fps). Every time michael@0: * it ticks, a single refresh driver is poked. Once they have all been poked, michael@0: * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point, michael@0: * the timer is quiet and doesn't tick (until something is added to it again). michael@0: * michael@0: * When a timer is removed, there is a possibility of another timer michael@0: * being skipped for one cycle. We could avoid this by adjusting michael@0: * mNextDriverIndex in RemoveRefreshDriver, but there's little need to michael@0: * add that complexity. All we want is for inactive drivers to tick michael@0: * at some point, but we don't care too much about how often. michael@0: */ michael@0: class InactiveRefreshDriverTimer : michael@0: public RefreshDriverTimer michael@0: { michael@0: public: michael@0: InactiveRefreshDriverTimer(double aRate) michael@0: : RefreshDriverTimer(aRate), michael@0: mNextTickDuration(aRate), michael@0: mDisableAfterMilliseconds(-1.0), michael@0: mNextDriverIndex(0) michael@0: { michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: } michael@0: michael@0: InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds) michael@0: : RefreshDriverTimer(aRate), michael@0: mNextTickDuration(aRate), michael@0: mDisableAfterMilliseconds(aDisableAfterMilliseconds), michael@0: mNextDriverIndex(0) michael@0: { michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: } michael@0: michael@0: virtual void AddRefreshDriver(nsRefreshDriver* aDriver) michael@0: { michael@0: RefreshDriverTimer::AddRefreshDriver(aDriver); michael@0: michael@0: LOG("[%p] inactive timer got new refresh driver %p, resetting rate", michael@0: this, aDriver); michael@0: michael@0: // reset the timer, and start with the newly added one next time. michael@0: mNextTickDuration = mRateMilliseconds; michael@0: michael@0: // we don't really have to start with the newly added one, but we may as well michael@0: // not tick the old ones at the fastest rate any more than we need to. michael@0: mNextDriverIndex = mRefreshDrivers.Length() - 1; michael@0: michael@0: StopTimer(); michael@0: StartTimer(); michael@0: } michael@0: michael@0: protected: michael@0: virtual void StartTimer() michael@0: { michael@0: mLastFireEpoch = JS_Now(); michael@0: mLastFireTime = TimeStamp::Now(); michael@0: michael@0: mTargetTime = mLastFireTime + mRateDuration; michael@0: michael@0: uint32_t delay = static_cast(mRateMilliseconds); michael@0: mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: virtual void StopTimer() michael@0: { michael@0: mTimer->Cancel(); michael@0: } michael@0: michael@0: virtual void ScheduleNextTick(TimeStamp aNowTime) michael@0: { michael@0: if (mDisableAfterMilliseconds > 0.0 && michael@0: mNextTickDuration > mDisableAfterMilliseconds) michael@0: { michael@0: // We hit the time after which we should disable michael@0: // inactive window refreshes; don't schedule anything michael@0: // until we get kicked by an AddRefreshDriver call. michael@0: return; michael@0: } michael@0: michael@0: // double the next tick time if we've already gone through all of them once michael@0: if (mNextDriverIndex >= mRefreshDrivers.Length()) { michael@0: mNextTickDuration *= 2.0; michael@0: mNextDriverIndex = 0; michael@0: } michael@0: michael@0: // this doesn't need to be precise; do a simple schedule michael@0: uint32_t delay = static_cast(mNextTickDuration); michael@0: mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT); michael@0: michael@0: LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration, michael@0: mNextDriverIndex, mRefreshDrivers.Length()); michael@0: } michael@0: michael@0: /* Runs just one driver's tick. */ michael@0: void TickOne() michael@0: { michael@0: int64_t jsnow = JS_Now(); michael@0: TimeStamp now = TimeStamp::Now(); michael@0: michael@0: ScheduleNextTick(now); michael@0: michael@0: mLastFireEpoch = jsnow; michael@0: mLastFireTime = now; michael@0: michael@0: nsTArray > drivers(mRefreshDrivers); michael@0: if (mNextDriverIndex < drivers.Length() && michael@0: !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled()) michael@0: { michael@0: TickDriver(drivers[mNextDriverIndex], jsnow, now); michael@0: } michael@0: michael@0: mNextDriverIndex++; michael@0: } michael@0: michael@0: static void TimerTickOne(nsITimer* aTimer, void* aClosure) michael@0: { michael@0: InactiveRefreshDriverTimer *timer = static_cast(aClosure); michael@0: timer->TickOne(); michael@0: } michael@0: michael@0: nsRefPtr mTimer; michael@0: double mNextTickDuration; michael@0: double mDisableAfterMilliseconds; michael@0: uint32_t mNextDriverIndex; michael@0: }; michael@0: michael@0: } // namespace mozilla michael@0: michael@0: static uint32_t michael@0: GetFirstFrameDelay(imgIRequest* req) michael@0: { michael@0: nsCOMPtr container; michael@0: if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) { michael@0: return 0; michael@0: } michael@0: michael@0: // If this image isn't animated, there isn't a first frame delay. michael@0: int32_t delay = container->GetFirstFrameDelay(); michael@0: if (delay < 0) michael@0: return 0; michael@0: michael@0: return static_cast(delay); michael@0: } michael@0: michael@0: static PreciseRefreshDriverTimer *sRegularRateTimer = nullptr; michael@0: static InactiveRefreshDriverTimer *sThrottledRateTimer = nullptr; michael@0: michael@0: #ifdef XP_WIN michael@0: static int32_t sHighPrecisionTimerRequests = 0; michael@0: // a bare pointer to avoid introducing a static constructor michael@0: static nsITimer *sDisableHighPrecisionTimersTimer = nullptr; michael@0: #endif michael@0: michael@0: /* static */ void michael@0: nsRefreshDriver::InitializeStatics() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gLog) { michael@0: gLog = PR_NewLogModule("nsRefreshDriver"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /* static */ void michael@0: nsRefreshDriver::Shutdown() michael@0: { michael@0: // clean up our timers michael@0: delete sRegularRateTimer; michael@0: delete sThrottledRateTimer; michael@0: michael@0: sRegularRateTimer = nullptr; michael@0: sThrottledRateTimer = nullptr; michael@0: michael@0: #ifdef XP_WIN michael@0: if (sDisableHighPrecisionTimersTimer) { michael@0: sDisableHighPrecisionTimersTimer->Cancel(); michael@0: NS_RELEASE(sDisableHighPrecisionTimersTimer); michael@0: timeEndPeriod(1); michael@0: } else if (sHighPrecisionTimerRequests) { michael@0: timeEndPeriod(1); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /* static */ int32_t michael@0: nsRefreshDriver::DefaultInterval() michael@0: { michael@0: return NSToIntRound(1000.0 / DEFAULT_FRAME_RATE); michael@0: } michael@0: michael@0: // Compute the interval to use for the refresh driver timer, in milliseconds. michael@0: // outIsDefault indicates that rate was not explicitly set by the user michael@0: // so we might choose other, more appropriate rates (e.g. vsync, etc) michael@0: // layout.frame_rate=0 indicates "ASAP mode". michael@0: // In ASAP mode rendering is iterated as fast as possible (typically for stress testing). michael@0: // A target rate of 10k is used internally instead of special-handling 0. michael@0: // Backends which block on swap/present/etc should try to not block michael@0: // when layout.frame_rate=0 - to comply with "ASAP" as much as possible. michael@0: double michael@0: nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const michael@0: { michael@0: int32_t rate = Preferences::GetInt("layout.frame_rate", -1); michael@0: if (rate < 0) { michael@0: rate = DEFAULT_FRAME_RATE; michael@0: if (outIsDefault) { michael@0: *outIsDefault = true; michael@0: } michael@0: } else { michael@0: if (outIsDefault) { michael@0: *outIsDefault = false; michael@0: } michael@0: } michael@0: michael@0: if (rate == 0) { michael@0: rate = 10000; michael@0: } michael@0: michael@0: return 1000.0 / rate; michael@0: } michael@0: michael@0: double michael@0: nsRefreshDriver::GetThrottledTimerInterval() const michael@0: { michael@0: int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1); michael@0: if (rate <= 0) { michael@0: rate = DEFAULT_THROTTLED_FRAME_RATE; michael@0: } michael@0: return 1000.0 / rate; michael@0: } michael@0: michael@0: double michael@0: nsRefreshDriver::GetRefreshTimerInterval() const michael@0: { michael@0: return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval(); michael@0: } michael@0: michael@0: RefreshDriverTimer* michael@0: nsRefreshDriver::ChooseTimer() const michael@0: { michael@0: if (mThrottled) { michael@0: if (!sThrottledRateTimer) michael@0: sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(), michael@0: DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0); michael@0: return sThrottledRateTimer; michael@0: } michael@0: michael@0: if (!sRegularRateTimer) { michael@0: bool isDefault = true; michael@0: double rate = GetRegularTimerInterval(&isDefault); michael@0: #ifdef XP_WIN michael@0: if (PreciseRefreshDriverTimerWindowsDwmVsync::IsSupported()) { michael@0: sRegularRateTimer = new PreciseRefreshDriverTimerWindowsDwmVsync(rate, isDefault); michael@0: } michael@0: #endif michael@0: if (!sRegularRateTimer) { michael@0: sRegularRateTimer = new PreciseRefreshDriverTimer(rate); michael@0: } michael@0: } michael@0: return sRegularRateTimer; michael@0: } michael@0: michael@0: nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) michael@0: : mActiveTimer(nullptr), michael@0: mPresContext(aPresContext), michael@0: mFreezeCount(0), michael@0: mThrottled(false), michael@0: mTestControllingRefreshes(false), michael@0: mViewManagerFlushIsPending(false), michael@0: mRequestedHighPrecision(false), michael@0: mInRefresh(false) michael@0: { michael@0: mMostRecentRefreshEpochTime = JS_Now(); michael@0: mMostRecentRefresh = TimeStamp::Now(); michael@0: } michael@0: michael@0: nsRefreshDriver::~nsRefreshDriver() michael@0: { michael@0: NS_ABORT_IF_FALSE(ObserverCount() == 0, michael@0: "observers should have unregistered"); michael@0: NS_ABORT_IF_FALSE(!mActiveTimer, "timer should be gone"); michael@0: michael@0: for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { michael@0: mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); michael@0: } michael@0: mPresShellsToInvalidateIfHidden.Clear(); michael@0: } michael@0: michael@0: // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh michael@0: // for description. michael@0: void michael@0: nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) michael@0: { michael@0: // ensure that we're removed from our driver michael@0: StopTimer(); michael@0: michael@0: if (!mTestControllingRefreshes) { michael@0: mMostRecentRefreshEpochTime = JS_Now(); michael@0: mMostRecentRefresh = TimeStamp::Now(); michael@0: michael@0: mTestControllingRefreshes = true; michael@0: } michael@0: michael@0: mMostRecentRefreshEpochTime += aMilliseconds * 1000; michael@0: mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds); michael@0: michael@0: mozilla::dom::AutoNoJSAPI nojsapi; michael@0: DoTick(); michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::RestoreNormalRefresh() michael@0: { michael@0: mTestControllingRefreshes = false; michael@0: EnsureTimerStarted(false); michael@0: } michael@0: michael@0: TimeStamp michael@0: nsRefreshDriver::MostRecentRefresh() const michael@0: { michael@0: const_cast(this)->EnsureTimerStarted(false); michael@0: michael@0: return mMostRecentRefresh; michael@0: } michael@0: michael@0: int64_t michael@0: nsRefreshDriver::MostRecentRefreshEpochTime() const michael@0: { michael@0: const_cast(this)->EnsureTimerStarted(false); michael@0: michael@0: return mMostRecentRefreshEpochTime; michael@0: } michael@0: michael@0: bool michael@0: nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, michael@0: mozFlushType aFlushType) michael@0: { michael@0: ObserverArray& array = ArrayFor(aFlushType); michael@0: bool success = array.AppendElement(aObserver) != nullptr; michael@0: EnsureTimerStarted(false); michael@0: return success; michael@0: } michael@0: michael@0: bool michael@0: nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, michael@0: mozFlushType aFlushType) michael@0: { michael@0: ObserverArray& array = ArrayFor(aFlushType); michael@0: return array.RemoveElement(aObserver); michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) michael@0: { michael@0: mPostRefreshObservers.AppendElement(aObserver); michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) michael@0: { michael@0: mPostRefreshObservers.RemoveElement(aObserver); michael@0: } michael@0: michael@0: bool michael@0: nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) michael@0: { michael@0: uint32_t delay = GetFirstFrameDelay(aRequest); michael@0: if (delay == 0) { michael@0: if (!mRequests.PutEntry(aRequest)) { michael@0: return false; michael@0: } michael@0: } else { michael@0: ImageStartData* start = mStartTable.Get(delay); michael@0: if (!start) { michael@0: start = new ImageStartData(); michael@0: mStartTable.Put(delay, start); michael@0: } michael@0: start->mEntries.PutEntry(aRequest); michael@0: } michael@0: michael@0: EnsureTimerStarted(false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest) michael@0: { michael@0: // Try to remove from both places, just in case, because we can't tell michael@0: // whether RemoveEntry() succeeds. michael@0: mRequests.RemoveEntry(aRequest); michael@0: uint32_t delay = GetFirstFrameDelay(aRequest); michael@0: if (delay != 0) { michael@0: ImageStartData* start = mStartTable.Get(delay); michael@0: if (start) { michael@0: start->mEntries.RemoveEntry(aRequest); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer) michael@0: { michael@0: if (mTestControllingRefreshes) michael@0: return; michael@0: michael@0: // will it already fire, and no other changes needed? michael@0: if (mActiveTimer && !aAdjustingTimer) michael@0: return; michael@0: michael@0: if (IsFrozen() || !mPresContext) { michael@0: // If we don't want to start it now, or we've been disconnected. michael@0: StopTimer(); michael@0: return; michael@0: } michael@0: michael@0: // We got here because we're either adjusting the time *or* we're michael@0: // starting it for the first time. Add to the right timer, michael@0: // prehaps removing it from a previously-set one. michael@0: RefreshDriverTimer *newTimer = ChooseTimer(); michael@0: if (newTimer != mActiveTimer) { michael@0: if (mActiveTimer) michael@0: mActiveTimer->RemoveRefreshDriver(this); michael@0: mActiveTimer = newTimer; michael@0: mActiveTimer->AddRefreshDriver(this); michael@0: } michael@0: michael@0: mMostRecentRefresh = mActiveTimer->MostRecentRefresh(); michael@0: mMostRecentRefreshEpochTime = mActiveTimer->MostRecentRefreshEpochTime(); michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::StopTimer() michael@0: { michael@0: if (!mActiveTimer) michael@0: return; michael@0: michael@0: mActiveTimer->RemoveRefreshDriver(this); michael@0: mActiveTimer = nullptr; michael@0: michael@0: if (mRequestedHighPrecision) { michael@0: SetHighPrecisionTimersEnabled(false); michael@0: } michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: static void michael@0: DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: timeEndPeriod(1); michael@0: NS_RELEASE(sDisableHighPrecisionTimersTimer); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsRefreshDriver::ConfigureHighPrecision() michael@0: { michael@0: bool haveFrameRequestCallbacks = mFrameRequestCallbackDocs.Length() > 0; michael@0: michael@0: // if the only change that's needed is that we need high precision, michael@0: // then just set that michael@0: if (!mThrottled && !mRequestedHighPrecision && haveFrameRequestCallbacks) { michael@0: SetHighPrecisionTimersEnabled(true); michael@0: } else if (mRequestedHighPrecision && !haveFrameRequestCallbacks) { michael@0: SetHighPrecisionTimersEnabled(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable) michael@0: { michael@0: LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false"); michael@0: michael@0: if (aEnable) { michael@0: NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!"); michael@0: #ifdef XP_WIN michael@0: if (++sHighPrecisionTimerRequests == 1) { michael@0: // If we had a timer scheduled to disable it, that means that it's already michael@0: // enabled; just cancel the timer. Otherwise, really enable it. michael@0: if (sDisableHighPrecisionTimersTimer) { michael@0: sDisableHighPrecisionTimersTimer->Cancel(); michael@0: NS_RELEASE(sDisableHighPrecisionTimersTimer); michael@0: } else { michael@0: timeBeginPeriod(1); michael@0: } michael@0: } michael@0: #endif michael@0: mRequestedHighPrecision = true; michael@0: } else { michael@0: NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!"); michael@0: #ifdef XP_WIN michael@0: if (--sHighPrecisionTimerRequests == 0) { michael@0: // Don't jerk us around between high precision and low precision michael@0: // timers; instead, only allow leaving high precision timers michael@0: // after 90 seconds. This is arbitrary, but hopefully good michael@0: // enough. michael@0: NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !"); michael@0: michael@0: nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: if (timer) { michael@0: timer.forget(&sDisableHighPrecisionTimersTimer); michael@0: sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback, michael@0: nullptr, michael@0: 90 * 1000, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } else { michael@0: // might happen if we're shutting down XPCOM; just drop the time period down michael@0: // immediately michael@0: timeEndPeriod(1); michael@0: } michael@0: } michael@0: #endif michael@0: mRequestedHighPrecision = false; michael@0: } michael@0: } michael@0: michael@0: uint32_t michael@0: nsRefreshDriver::ObserverCount() const michael@0: { michael@0: uint32_t sum = 0; michael@0: for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { michael@0: sum += mObservers[i].Length(); michael@0: } michael@0: michael@0: // Even while throttled, we need to process layout and style changes. Style michael@0: // changes can trigger transitions which fire events when they complete, and michael@0: // layout changes can affect media queries on child documents, triggering michael@0: // style changes, etc. michael@0: sum += mStyleFlushObservers.Length(); michael@0: sum += mLayoutFlushObservers.Length(); michael@0: sum += mFrameRequestCallbackDocs.Length(); michael@0: sum += mViewManagerFlushIsPending; michael@0: return sum; michael@0: } michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsRefreshDriver::StartTableRequestCounter(const uint32_t& aKey, michael@0: ImageStartData* aEntry, michael@0: void* aUserArg) michael@0: { michael@0: uint32_t *count = static_cast(aUserArg); michael@0: *count += aEntry->mEntries.Count(); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: uint32_t michael@0: nsRefreshDriver::ImageRequestCount() const michael@0: { michael@0: uint32_t count = 0; michael@0: mStartTable.EnumerateRead(nsRefreshDriver::StartTableRequestCounter, &count); michael@0: return count + mRequests.Count(); michael@0: } michael@0: michael@0: nsRefreshDriver::ObserverArray& michael@0: nsRefreshDriver::ArrayFor(mozFlushType aFlushType) michael@0: { michael@0: switch (aFlushType) { michael@0: case Flush_Style: michael@0: return mObservers[0]; michael@0: case Flush_Layout: michael@0: return mObservers[1]; michael@0: case Flush_Display: michael@0: return mObservers[2]; michael@0: default: michael@0: NS_ABORT_IF_FALSE(false, "bad flush type"); michael@0: return *static_cast(nullptr); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * nsISupports implementation michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsRefreshDriver, nsISupports) michael@0: michael@0: /* michael@0: * nsITimerCallback implementation michael@0: */ michael@0: michael@0: void michael@0: nsRefreshDriver::DoTick() michael@0: { michael@0: NS_PRECONDITION(!IsFrozen(), "Why are we notified while frozen?"); michael@0: NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?"); michael@0: NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), michael@0: "Shouldn't have a JSContext on the stack"); michael@0: michael@0: if (mTestControllingRefreshes) { michael@0: Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh); michael@0: } else { michael@0: Tick(JS_Now(), TimeStamp::Now()); michael@0: } michael@0: } michael@0: michael@0: struct DocumentFrameCallbacks { michael@0: DocumentFrameCallbacks(nsIDocument* aDocument) : michael@0: mDocument(aDocument) michael@0: {} michael@0: michael@0: nsCOMPtr mDocument; michael@0: nsIDocument::FrameRequestCallbackList mCallbacks; michael@0: }; michael@0: michael@0: void michael@0: nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) michael@0: { michael@0: NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(), michael@0: "Shouldn't have a JSContext on the stack"); michael@0: michael@0: if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) { michael@0: NS_ERROR("Refresh driver should not run during plugin call!"); michael@0: // Try to survive this by just ignoring the refresh tick. michael@0: return; michael@0: } michael@0: michael@0: PROFILER_LABEL("nsRefreshDriver", "Tick"); michael@0: michael@0: // We're either frozen or we were disconnected (likely in the middle michael@0: // of a tick iteration). Just do nothing here, since our michael@0: // prescontext went away. michael@0: if (IsFrozen() || !mPresContext) { michael@0: return; michael@0: } michael@0: michael@0: TimeStamp previousRefresh = mMostRecentRefresh; michael@0: michael@0: mMostRecentRefresh = aNowTime; michael@0: mMostRecentRefreshEpochTime = aNowEpoch; michael@0: michael@0: nsCOMPtr presShell = mPresContext->GetPresShell(); michael@0: if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) { michael@0: // Things are being destroyed, or we no longer have any observers. michael@0: // We don't want to stop the timer when observers are initially michael@0: // removed, because sometimes observers can be added and removed michael@0: // often depending on what other things are going on and in that michael@0: // situation we don't want to thrash our timer. So instead we michael@0: // wait until we get a Notify() call when we have no observers michael@0: // before stopping the timer. michael@0: StopTimer(); michael@0: return; michael@0: } michael@0: michael@0: profiler_tracing("Paint", "RD", TRACING_INTERVAL_START); michael@0: michael@0: AutoRestore restoreInRefresh(mInRefresh); michael@0: mInRefresh = true; michael@0: michael@0: /* michael@0: * The timer holds a reference to |this| while calling |Notify|. michael@0: * However, implementations of |WillRefresh| are permitted to destroy michael@0: * the pres context, which will cause our |mPresContext| to become michael@0: * null. If this happens, we must stop notifying observers. michael@0: */ michael@0: for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { michael@0: ObserverArray::EndLimitedIterator etor(mObservers[i]); michael@0: while (etor.HasMore()) { michael@0: nsRefPtr obs = etor.GetNext(); michael@0: obs->WillRefresh(aNowTime); michael@0: michael@0: if (!mPresContext || !mPresContext->GetPresShell()) { michael@0: StopTimer(); michael@0: profiler_tracing("Paint", "RD", TRACING_INTERVAL_END); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (i == 0) { michael@0: // Grab all of our frame request callbacks up front. michael@0: nsTArray michael@0: frameRequestCallbacks(mFrameRequestCallbackDocs.Length()); michael@0: for (uint32_t i = 0; i < mFrameRequestCallbackDocs.Length(); ++i) { michael@0: frameRequestCallbacks.AppendElement(mFrameRequestCallbackDocs[i]); michael@0: mFrameRequestCallbackDocs[i]-> michael@0: TakeFrameRequestCallbacks(frameRequestCallbacks.LastElement().mCallbacks); michael@0: } michael@0: // OK, now reset mFrameRequestCallbackDocs so they can be michael@0: // readded as needed. michael@0: mFrameRequestCallbackDocs.Clear(); michael@0: michael@0: profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START); michael@0: int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; michael@0: for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) { michael@0: const DocumentFrameCallbacks& docCallbacks = frameRequestCallbacks[i]; michael@0: // XXXbz Bug 863140: GetInnerWindow can return the outer michael@0: // window in some cases. michael@0: nsPIDOMWindow* innerWindow = docCallbacks.mDocument->GetInnerWindow(); michael@0: DOMHighResTimeStamp timeStamp = 0; michael@0: if (innerWindow && innerWindow->IsInnerWindow()) { michael@0: nsPerformance* perf = innerWindow->GetPerformance(); michael@0: if (perf) { michael@0: timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime); michael@0: } michael@0: // else window is partially torn down already michael@0: } michael@0: for (uint32_t j = 0; j < docCallbacks.mCallbacks.Length(); ++j) { michael@0: const nsIDocument::FrameRequestCallbackHolder& holder = michael@0: docCallbacks.mCallbacks[j]; michael@0: nsAutoMicroTask mt; michael@0: if (holder.HasWebIDLCallback()) { michael@0: ErrorResult ignored; michael@0: holder.GetWebIDLCallback()->Call(timeStamp, ignored); michael@0: } else { michael@0: holder.GetXPCOMCallback()->Sample(eventTime); michael@0: } michael@0: } michael@0: } michael@0: profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END); michael@0: michael@0: // This is the Flush_Style case. michael@0: if (mPresContext && mPresContext->GetPresShell()) { michael@0: nsAutoTArray observers; michael@0: observers.AppendElements(mStyleFlushObservers); michael@0: for (uint32_t j = observers.Length(); michael@0: j && mPresContext && mPresContext->GetPresShell(); --j) { michael@0: // Make sure to not process observers which might have been removed michael@0: // during previous iterations. michael@0: nsIPresShell* shell = observers[j - 1]; michael@0: if (!mStyleFlushObservers.Contains(shell)) michael@0: continue; michael@0: NS_ADDREF(shell); michael@0: mStyleFlushObservers.RemoveElement(shell); michael@0: shell->GetPresContext()->RestyleManager()->mObservingRefreshDriver = false; michael@0: shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false)); michael@0: NS_RELEASE(shell); michael@0: } michael@0: } michael@0: } else if (i == 1) { michael@0: // This is the Flush_Layout case. michael@0: if (mPresContext && mPresContext->GetPresShell()) { michael@0: nsAutoTArray observers; michael@0: observers.AppendElements(mLayoutFlushObservers); michael@0: for (uint32_t j = observers.Length(); michael@0: j && mPresContext && mPresContext->GetPresShell(); --j) { michael@0: // Make sure to not process observers which might have been removed michael@0: // during previous iterations. michael@0: nsIPresShell* shell = observers[j - 1]; michael@0: if (!mLayoutFlushObservers.Contains(shell)) michael@0: continue; michael@0: NS_ADDREF(shell); michael@0: mLayoutFlushObservers.RemoveElement(shell); michael@0: shell->mReflowScheduled = false; michael@0: shell->mSuppressInterruptibleReflows = false; michael@0: shell->FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout, michael@0: false)); michael@0: NS_RELEASE(shell); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Perform notification to imgIRequests subscribed to listen michael@0: * for refresh events. michael@0: */ michael@0: michael@0: ImageRequestParameters parms = {aNowTime, previousRefresh, &mRequests}; michael@0: michael@0: mStartTable.EnumerateRead(nsRefreshDriver::StartTableRefresh, &parms); michael@0: michael@0: if (mRequests.Count()) { michael@0: // RequestRefresh may run scripts, so it's not safe to directly call it michael@0: // while using a hashtable enumerator to enumerate mRequests in case michael@0: // script modifies the hashtable. Instead, we build a (local) array of michael@0: // images to refresh, and then we refresh each image in that array. michael@0: nsCOMArray imagesToRefresh(mRequests.Count()); michael@0: mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, michael@0: &imagesToRefresh); michael@0: michael@0: for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) { michael@0: imagesToRefresh[i]->RequestRefresh(aNowTime); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { michael@0: mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); michael@0: } michael@0: mPresShellsToInvalidateIfHidden.Clear(); michael@0: michael@0: if (mViewManagerFlushIsPending) { michael@0: #ifdef MOZ_DUMP_PAINTING michael@0: if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { michael@0: printf_stderr("Starting ProcessPendingUpdates\n"); michael@0: } michael@0: #endif michael@0: michael@0: mViewManagerFlushIsPending = false; michael@0: nsRefPtr vm = mPresContext->GetPresShell()->GetViewManager(); michael@0: vm->ProcessPendingUpdates(); michael@0: #ifdef MOZ_DUMP_PAINTING michael@0: if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { michael@0: printf_stderr("Ending ProcessPendingUpdates\n"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) { michael@0: mPostRefreshObservers[i]->DidRefresh(); michael@0: } michael@0: profiler_tracing("Paint", "RD", TRACING_INTERVAL_END); michael@0: michael@0: NS_ASSERTION(mInRefresh, "Still in refresh"); michael@0: } michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry, michael@0: void* aUserArg) michael@0: { michael@0: nsCOMArray* imagesToRefresh = michael@0: static_cast*> (aUserArg); michael@0: imgIRequest* req = static_cast(aEntry->GetKey()); michael@0: NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request"); michael@0: nsCOMPtr image; michael@0: if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { michael@0: imagesToRefresh->AppendElement(image); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsRefreshDriver::BeginRefreshingImages(nsISupportsHashKey* aEntry, michael@0: void* aUserArg) michael@0: { michael@0: ImageRequestParameters* parms = michael@0: static_cast (aUserArg); michael@0: michael@0: imgIRequest* req = static_cast(aEntry->GetKey()); michael@0: NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request"); michael@0: michael@0: parms->mRequests->PutEntry(req); michael@0: michael@0: nsCOMPtr image; michael@0: if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { michael@0: image->SetAnimationStartTime(parms->mDesired); michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsRefreshDriver::StartTableRefresh(const uint32_t& aDelay, michael@0: ImageStartData* aData, michael@0: void* aUserArg) michael@0: { michael@0: ImageRequestParameters* parms = michael@0: static_cast (aUserArg); michael@0: michael@0: if (!aData->mStartTime.empty()) { michael@0: TimeStamp& start = aData->mStartTime.ref(); michael@0: TimeDuration prev = parms->mPrevious - start; michael@0: TimeDuration curr = parms->mCurrent - start; michael@0: uint32_t prevMultiple = static_cast(prev.ToMilliseconds()) / aDelay; michael@0: michael@0: // We want to trigger images' refresh if we've just crossed over a multiple michael@0: // of the first image's start time. If so, set the animation start time to michael@0: // the nearest multiple of the delay and move all the images in this table michael@0: // to the main requests table. michael@0: if (prevMultiple != static_cast(curr.ToMilliseconds()) / aDelay) { michael@0: parms->mDesired = start + TimeDuration::FromMilliseconds(prevMultiple * aDelay); michael@0: aData->mEntries.EnumerateEntries(nsRefreshDriver::BeginRefreshingImages, parms); michael@0: } michael@0: } else { michael@0: // This is the very first time we've drawn images with this time delay. michael@0: // Set the animation start time to "now" and move all the images in this michael@0: // table to the main requests table. michael@0: parms->mDesired = parms->mCurrent; michael@0: aData->mEntries.EnumerateEntries(nsRefreshDriver::BeginRefreshingImages, parms); michael@0: aData->mStartTime.construct(parms->mCurrent); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::Freeze() michael@0: { michael@0: StopTimer(); michael@0: mFreezeCount++; michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::Thaw() michael@0: { michael@0: NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver"); michael@0: michael@0: if (mFreezeCount > 0) { michael@0: mFreezeCount--; michael@0: } michael@0: michael@0: if (mFreezeCount == 0) { michael@0: if (ObserverCount() || ImageRequestCount()) { michael@0: // FIXME: This isn't quite right, since our EnsureTimerStarted call michael@0: // updates our mMostRecentRefresh, but the DoRefresh call won't run michael@0: // and notify our observers until we get back to the event loop. michael@0: // Thus MostRecentRefresh() will lie between now and the DoRefresh. michael@0: NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh)); michael@0: EnsureTimerStarted(false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::SetThrottled(bool aThrottled) michael@0: { michael@0: if (aThrottled != mThrottled) { michael@0: mThrottled = aThrottled; michael@0: if (mActiveTimer) { michael@0: // We want to switch our timer type here, so just stop and michael@0: // restart the timer. michael@0: EnsureTimerStarted(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::DoRefresh() michael@0: { michael@0: // Don't do a refresh unless we're in a state where we should be refreshing. michael@0: if (!IsFrozen() && mPresContext && mActiveTimer) { michael@0: DoTick(); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: bool michael@0: nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver, michael@0: mozFlushType aFlushType) michael@0: { michael@0: ObserverArray& array = ArrayFor(aFlushType); michael@0: return array.Contains(aObserver); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsRefreshDriver::ScheduleViewManagerFlush() michael@0: { michael@0: NS_ASSERTION(mPresContext->IsRoot(), michael@0: "Should only schedule view manager flush on root prescontexts"); michael@0: mViewManagerFlushIsPending = true; michael@0: EnsureTimerStarted(false); michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) michael@0: { michael@0: NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) == michael@0: mFrameRequestCallbackDocs.NoIndex, michael@0: "Don't schedule the same document multiple times"); michael@0: mFrameRequestCallbackDocs.AppendElement(aDocument); michael@0: michael@0: // make sure that the timer is running michael@0: ConfigureHighPrecision(); michael@0: EnsureTimerStarted(false); michael@0: } michael@0: michael@0: void michael@0: nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument) michael@0: { michael@0: mFrameRequestCallbackDocs.RemoveElement(aDocument); michael@0: ConfigureHighPrecision(); michael@0: // No need to worry about restarting our timer in slack mode if it's already michael@0: // running; that will happen automatically when it fires. michael@0: } michael@0: michael@0: #undef LOG