summaryrefslogtreecommitdiffstats
path: root/layout/base/nsRefreshDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base/nsRefreshDriver.cpp')
-rw-r--r--layout/base/nsRefreshDriver.cpp2373
1 files changed, 2373 insertions, 0 deletions
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
new file mode 100644
index 000000000..6676bea97
--- /dev/null
+++ b/layout/base/nsRefreshDriver.cpp
@@ -0,0 +1,2373 @@
+/* -*- 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>
+#include "WinUtils.h"
+#endif
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/IntegerRange.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsRefreshDriver.h"
+#include "nsITimer.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+#include "nsAutoPtr.h"
+#include "nsIDocument.h"
+#include "jsapi.h"
+#include "nsContentUtils.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/Preferences.h"
+#include "nsViewManager.h"
+#include "GeckoProfiler.h"
+#include "nsNPAPIPluginInstance.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "Layers.h"
+#include "imgIContainer.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsDocShell.h"
+#include "nsISimpleEnumerator.h"
+#include "nsJSEnvironment.h"
+#include "mozilla/Telemetry.h"
+#include "gfxPrefs.h"
+#include "BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "mozilla/layout/VsyncChild.h"
+#include "VsyncSource.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/TimelineConsumers.h"
+#include "nsAnimationManager.h"
+#include "nsIDOMEvent.h"
+#include "nsDisplayList.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using namespace mozilla::ipc;
+using namespace mozilla::layout;
+
+static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
+#define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+#define DEFAULT_THROTTLED_FRAME_RATE 1
+#define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000
+#define DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS 100
+// after 10 minutes, stop firing off inactive timers
+#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
+
+// The number of seconds spent skipping frames because we are waiting for the compositor
+// before logging.
+#if defined(MOZ_ASAN)
+# define REFRESH_WAIT_WARNING 5
+#elif defined(DEBUG) && !defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING 5
+#elif defined(DEBUG) && defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5)
+#elif defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1)
+#else
+# define REFRESH_WAIT_WARNING 1
+#endif
+
+namespace {
+ // `true` if we are currently in jank-critical mode.
+ //
+ // In jank-critical mode, any iteration of the event loop that takes
+ // more than 16ms to compute will cause an ongoing animation to miss
+ // frames.
+ //
+ // For simplicity, the current implementation assumes that we are in
+ // jank-critical mode if and only if at least one vsync driver has
+ // at least one observer.
+ static uint64_t sActiveVsyncTimers = 0;
+
+ // The latest value of process-wide jank levels.
+ //
+ // For each i, sJankLevels[i] counts the number of times delivery of
+ // vsync to the main thread has been delayed by at least 2^i ms. Use
+ // GetJankLevels to grab a copy of this array.
+ uint64_t sJankLevels[12];
+
+ // The number outstanding nsRefreshDrivers (that have been created but not
+ // disconnected). When this reaches zero we will call
+ // nsRefreshDriver::Shutdown.
+ static uint32_t sRefreshDriverCount = 0;
+}
+
+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:
+ RefreshDriverTimer()
+ : mLastFireEpoch(0)
+ , mLastFireSkipped(false)
+ {
+ }
+
+ virtual ~RefreshDriverTimer()
+ {
+ MOZ_ASSERT(mContentRefreshDrivers.Length() == 0, "Should have removed all content refresh drivers from here by now!");
+ MOZ_ASSERT(mRootRefreshDrivers.Length() == 0, "Should have removed all root refresh drivers from here by now!");
+ }
+
+ virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
+ {
+ LOG("[%p] AddRefreshDriver %p", this, aDriver);
+
+ bool startTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
+ if (IsRootRefreshDriver(aDriver)) {
+ NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver), "Adding a duplicate root refresh driver!");
+ mRootRefreshDrivers.AppendElement(aDriver);
+ } else {
+ NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver), "Adding a duplicate content refresh driver!");
+ mContentRefreshDrivers.AppendElement(aDriver);
+ }
+
+ if (startTimer) {
+ StartTimer();
+ }
+ }
+
+ virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver)
+ {
+ LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
+
+ if (IsRootRefreshDriver(aDriver)) {
+ NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the root refresh list!");
+ mRootRefreshDrivers.RemoveElement(aDriver);
+ } else {
+ nsPresContext* pc = aDriver->GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ // During PresContext shutdown, we can't accurately detect
+ // if a root refresh driver exists or not. Therefore, we have to
+ // search and find out which list this driver exists in.
+ if (!rootContext) {
+ if (mRootRefreshDrivers.Contains(aDriver)) {
+ mRootRefreshDrivers.RemoveElement(aDriver);
+ } else {
+ NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
+ "RemoveRefreshDriver without a display root for a driver that is not in the content refresh list");
+ mContentRefreshDrivers.RemoveElement(aDriver);
+ }
+ } else {
+ NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a driver that is not in the content refresh list");
+ mContentRefreshDrivers.RemoveElement(aDriver);
+ }
+ }
+
+ bool stopTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
+ if (stopTimer) {
+ StopTimer();
+ }
+ }
+
+ TimeStamp MostRecentRefresh() const { return mLastFireTime; }
+ int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; }
+
+ void SwapRefreshDrivers(RefreshDriverTimer* aNewTimer)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (nsRefreshDriver* driver : mContentRefreshDrivers) {
+ aNewTimer->AddRefreshDriver(driver);
+ driver->mActiveTimer = aNewTimer;
+ }
+ mContentRefreshDrivers.Clear();
+
+ for (nsRefreshDriver* driver : mRootRefreshDrivers) {
+ aNewTimer->AddRefreshDriver(driver);
+ driver->mActiveTimer = aNewTimer;
+ }
+ mRootRefreshDrivers.Clear();
+
+ aNewTimer->mLastFireEpoch = mLastFireEpoch;
+ aNewTimer->mLastFireTime = mLastFireTime;
+ }
+
+ virtual TimeDuration GetTimerRate() = 0;
+
+ bool LastTickSkippedAnyPaints() const
+ {
+ return mLastFireSkipped;
+ }
+
+ Maybe<TimeStamp> GetIdleDeadlineHint()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (LastTickSkippedAnyPaints()) {
+ return Some(TimeStamp());
+ }
+
+ TimeStamp mostRecentRefresh = MostRecentRefresh();
+ TimeDuration refreshRate = GetTimerRate();
+ TimeStamp idleEnd = mostRecentRefresh + refreshRate;
+
+ if (idleEnd +
+ refreshRate * nsLayoutUtils::QuiescentFramesBeforeIdlePeriod() <
+ TimeStamp::Now()) {
+ return Nothing();
+ }
+
+ return Some(idleEnd - TimeDuration::FromMilliseconds(
+ nsLayoutUtils::IdlePeriodDeadlineLimit()));
+ }
+
+protected:
+ virtual void StartTimer() = 0;
+ virtual void StopTimer() = 0;
+ virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
+
+ bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
+ {
+ nsPresContext* pc = aDriver->GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ if (!rootContext) {
+ return false;
+ }
+
+ return aDriver == rootContext->RefreshDriver();
+ }
+
+ /*
+ * 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();
+ Tick(jsnow, now);
+ }
+
+ void TickRefreshDrivers(int64_t aJsNow, TimeStamp aNow, nsTArray<RefPtr<nsRefreshDriver>>& aDrivers)
+ {
+ if (aDrivers.IsEmpty()) {
+ return;
+ }
+
+ nsTArray<RefPtr<nsRefreshDriver> > drivers(aDrivers);
+ for (nsRefreshDriver* driver : drivers) {
+ // don't poke this driver if it's in test mode
+ if (driver->IsTestControllingRefreshesEnabled()) {
+ continue;
+ }
+
+ TickDriver(driver, aJsNow, aNow);
+
+ mLastFireSkipped = mLastFireSkipped || driver->mSkippedPaints;
+ }
+ }
+
+ /*
+ * Tick the refresh drivers based on the given timestamp.
+ */
+ void Tick(int64_t jsnow, TimeStamp now)
+ {
+ ScheduleNextTick(now);
+
+ mLastFireEpoch = jsnow;
+ mLastFireTime = now;
+ mLastFireSkipped = false;
+
+ LOG("[%p] ticking drivers...", this);
+ // RD is short for RefreshDriver
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
+
+ TickRefreshDrivers(jsnow, now, mContentRefreshDrivers);
+ TickRefreshDrivers(jsnow, now, mRootRefreshDrivers);
+
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
+ 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);
+ }
+
+ int64_t mLastFireEpoch;
+ bool mLastFireSkipped;
+ TimeStamp mLastFireTime;
+ TimeStamp mTargetTime;
+
+ nsTArray<RefPtr<nsRefreshDriver> > mContentRefreshDrivers;
+ nsTArray<RefPtr<nsRefreshDriver> > mRootRefreshDrivers;
+
+ // 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:
+ /*
+ * aRate -- the delay, in milliseconds, requested between timer firings
+ */
+ explicit SimpleTimerBasedRefreshDriverTimer(double aRate)
+ {
+ SetRate(aRate);
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ ~SimpleTimerBasedRefreshDriverTimer() override
+ {
+ StopTimer();
+ }
+
+ // will take effect at next timer tick
+ virtual void SetRate(double aNewRate)
+ {
+ mRateMilliseconds = aNewRate;
+ mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
+ }
+
+ double GetRate() const
+ {
+ return mRateMilliseconds;
+ }
+
+ TimeDuration GetTimerRate() override
+ {
+ return mRateDuration;
+ }
+
+protected:
+
+ void StartTimer() override
+ {
+ // 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);
+ }
+
+ void StopTimer() override
+ {
+ mTimer->Cancel();
+ }
+
+ double mRateMilliseconds;
+ TimeDuration mRateDuration;
+ RefPtr<nsITimer> mTimer;
+};
+
+/*
+ * A refresh driver that listens to vsync events and ticks the refresh driver
+ * on vsync intervals. We throttle the refresh driver if we get too many
+ * vsync events and wait to catch up again.
+ */
+class VsyncRefreshDriverTimer : public RefreshDriverTimer
+{
+public:
+ VsyncRefreshDriverTimer()
+ : mVsyncChild(nullptr)
+ {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ mVsyncObserver = new RefreshDriverVsyncObserver(this);
+ RefPtr<mozilla::gfx::VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync();
+ MOZ_ALWAYS_TRUE(mVsyncDispatcher = vsyncSource->GetRefreshTimerVsyncDispatcher());
+ mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
+ mVsyncRate = vsyncSource->GetGlobalDisplay().GetVsyncRate();
+ }
+
+ explicit VsyncRefreshDriverTimer(VsyncChild* aVsyncChild)
+ : mVsyncChild(aVsyncChild)
+ {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mVsyncChild);
+ mVsyncObserver = new RefreshDriverVsyncObserver(this);
+ mVsyncChild->SetVsyncObserver(mVsyncObserver);
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ TimeDuration GetTimerRate() override
+ {
+ if (mVsyncRate != TimeDuration::Forever()) {
+ return mVsyncRate;
+ }
+
+ if (mVsyncChild) {
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ // If hardware queries fail / are unsupported, we have to just guess.
+ return mVsyncRate != TimeDuration::Forever()
+ ? mVsyncRate
+ : TimeDuration::FromMilliseconds(1000.0 / 60.0);
+ }
+
+private:
+ // Since VsyncObservers are refCounted, but the RefreshDriverTimer are
+ // explicitly shutdown. We create an inner class that has the VsyncObserver
+ // and is shutdown when the RefreshDriverTimer is deleted. The alternative is
+ // to (a) make all RefreshDriverTimer RefCounted or (b) use different
+ // VsyncObserver types.
+ class RefreshDriverVsyncObserver final : public VsyncObserver
+ {
+ public:
+ explicit RefreshDriverVsyncObserver(VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
+ : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer)
+ , mRefreshTickLock("RefreshTickLock")
+ , mRecentVsync(TimeStamp::Now())
+ , mLastChildTick(TimeStamp::Now())
+ , mVsyncRate(TimeDuration::Forever())
+ , mProcessedVsync(true)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool NotifyVsync(TimeStamp aVsyncTimestamp) override
+ {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Compress vsync notifications such that only 1 may run at a time
+ // This is so that we don't flood the refresh driver with vsync messages
+ // if the main thread is blocked for long periods of time
+ { // scope lock
+ MonitorAutoLock lock(mRefreshTickLock);
+ mRecentVsync = aVsyncTimestamp;
+ if (!mProcessedVsync) {
+ return true;
+ }
+ mProcessedVsync = false;
+ }
+
+ nsCOMPtr<nsIRunnable> vsyncEvent =
+ NewRunnableMethod<TimeStamp>(this,
+ &RefreshDriverVsyncObserver::TickRefreshDriver,
+ aVsyncTimestamp);
+ NS_DispatchToMainThread(vsyncEvent);
+ } else {
+ TickRefreshDriver(aVsyncTimestamp);
+ }
+
+ return true;
+ }
+
+ void Shutdown()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mVsyncRefreshDriverTimer = nullptr;
+ }
+
+ void OnTimerStart()
+ {
+ if (!XRE_IsParentProcess()) {
+ mLastChildTick = TimeStamp::Now();
+ }
+ }
+ private:
+ ~RefreshDriverVsyncObserver() = default;
+
+ void RecordTelemetryProbes(TimeStamp aVsyncTimestamp)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ #ifndef ANDROID /* bug 1142079 */
+ if (XRE_IsParentProcess()) {
+ TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
+ uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,
+ sample);
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
+ sample);
+ RecordJank(sample);
+ } else if (mVsyncRate != TimeDuration::Forever()) {
+ TimeDuration contentDelay = (TimeStamp::Now() - mLastChildTick) - mVsyncRate;
+ if (contentDelay.ToMilliseconds() < 0 ){
+ // Vsyncs are noisy and some can come at a rate quicker than
+ // the reported hardware rate. In those cases, consider that we have 0 delay.
+ contentDelay = TimeDuration::FromMilliseconds(0);
+ }
+ uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,
+ sample);
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
+ sample);
+ RecordJank(sample);
+ } else {
+ // Request the vsync rate from the parent process. Might be a few vsyncs
+ // until the parent responds.
+ mVsyncRate = mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate();
+ }
+ #endif
+ }
+
+ void RecordJank(uint32_t aJankMS)
+ {
+ uint32_t duration = 1 /* ms */;
+ for (size_t i = 0;
+ i < mozilla::ArrayLength(sJankLevels) && duration < aJankMS;
+ ++i, duration *= 2) {
+ sJankLevels[i]++;
+ }
+ }
+
+ void TickRefreshDriver(TimeStamp aVsyncTimestamp)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RecordTelemetryProbes(aVsyncTimestamp);
+ if (XRE_IsParentProcess()) {
+ MonitorAutoLock lock(mRefreshTickLock);
+ aVsyncTimestamp = mRecentVsync;
+ mProcessedVsync = true;
+ } else {
+ mLastChildTick = TimeStamp::Now();
+ }
+ MOZ_ASSERT(aVsyncTimestamp <= TimeStamp::Now());
+
+ // We might have a problem that we call ~VsyncRefreshDriverTimer() before
+ // the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
+ // before use.
+ if (mVsyncRefreshDriverTimer) {
+ mVsyncRefreshDriverTimer->RunRefreshDrivers(aVsyncTimestamp);
+ }
+ }
+
+ // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
+ // be always available before Shutdown(). We can just use the raw pointer
+ // here.
+ VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
+ Monitor mRefreshTickLock;
+ TimeStamp mRecentVsync;
+ TimeStamp mLastChildTick;
+ TimeDuration mVsyncRate;
+ bool mProcessedVsync;
+ }; // RefreshDriverVsyncObserver
+
+ ~VsyncRefreshDriverTimer() override
+ {
+ if (XRE_IsParentProcess()) {
+ mVsyncDispatcher->SetParentRefreshTimer(nullptr);
+ mVsyncDispatcher = nullptr;
+ } else {
+ // Since the PVsyncChild actors live through the life of the process, just
+ // send the unobserveVsync message to disable vsync event. We don't need
+ // to handle the cleanup stuff of this actor. PVsyncChild::ActorDestroy()
+ // will be called and clean up this actor.
+ Unused << mVsyncChild->SendUnobserve();
+ mVsyncChild->SetVsyncObserver(nullptr);
+ mVsyncChild = nullptr;
+ }
+
+ // Detach current vsync timer from this VsyncObserver. The observer will no
+ // longer tick this timer.
+ mVsyncObserver->Shutdown();
+ mVsyncObserver = nullptr;
+ }
+
+ void StartTimer() override
+ {
+ // Protect updates to `sActiveVsyncTimers`.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mLastFireEpoch = JS_Now();
+ mLastFireTime = TimeStamp::Now();
+
+ if (XRE_IsParentProcess()) {
+ mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
+ } else {
+ Unused << mVsyncChild->SendObserve();
+ mVsyncObserver->OnTimerStart();
+ }
+
+ ++sActiveVsyncTimers;
+ }
+
+ void StopTimer() override
+ {
+ // Protect updates to `sActiveVsyncTimers`.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_IsParentProcess()) {
+ mVsyncDispatcher->SetParentRefreshTimer(nullptr);
+ } else {
+ Unused << mVsyncChild->SendUnobserve();
+ }
+
+ MOZ_ASSERT(sActiveVsyncTimers > 0);
+ --sActiveVsyncTimers;
+ }
+
+ void ScheduleNextTick(TimeStamp aNowTime) override
+ {
+ // Do nothing since we just wait for the next vsync from
+ // RefreshDriverVsyncObserver.
+ }
+
+ void RunRefreshDrivers(TimeStamp aTimeStamp)
+ {
+ int64_t jsnow = JS_Now();
+ TimeDuration diff = TimeStamp::Now() - aTimeStamp;
+ int64_t vsyncJsNow = jsnow - diff.ToMicroseconds();
+ Tick(vsyncJsNow, aTimeStamp);
+ }
+
+ RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
+ // Used for parent process.
+ RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
+ // Used for child process.
+ // The mVsyncChild will be always available before VsncChild::ActorDestroy().
+ // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
+ RefPtr<VsyncChild> mVsyncChild;
+ TimeDuration mVsyncRate;
+}; // VsyncRefreshDriverTimer
+
+/**
+ * Since the content process takes some time to setup
+ * the vsync IPC connection, this timer is used
+ * during the intial startup process.
+ * During initial startup, the refresh drivers
+ * are ticked off this timer, and are swapped out once content
+ * vsync IPC connection is established.
+ */
+class StartupRefreshDriverTimer :
+ public SimpleTimerBasedRefreshDriverTimer
+{
+public:
+ explicit StartupRefreshDriverTimer(double aRate)
+ : SimpleTimerBasedRefreshDriverTimer(aRate)
+ {
+ }
+
+protected:
+ void ScheduleNextTick(TimeStamp aNowTime) override
+ {
+ // Since this is only used for startup, it isn't super critical
+ // that we tick at consistent intervals.
+ TimeStamp newTarget = aNowTime + mRateDuration;
+ uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
+ 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 final :
+ public SimpleTimerBasedRefreshDriverTimer
+{
+public:
+ explicit InactiveRefreshDriverTimer(double aRate)
+ : SimpleTimerBasedRefreshDriverTimer(aRate),
+ mNextTickDuration(aRate),
+ mDisableAfterMilliseconds(-1.0),
+ mNextDriverIndex(0)
+ {
+ }
+
+ InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
+ : SimpleTimerBasedRefreshDriverTimer(aRate),
+ mNextTickDuration(aRate),
+ mDisableAfterMilliseconds(aDisableAfterMilliseconds),
+ mNextDriverIndex(0)
+ {
+ }
+
+ void AddRefreshDriver(nsRefreshDriver* aDriver) override
+ {
+ 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 = GetRefreshDriverCount() - 1;
+
+ StopTimer();
+ StartTimer();
+ }
+
+ TimeDuration GetTimerRate() override
+ {
+ return TimeDuration::FromMilliseconds(mNextTickDuration);
+ }
+
+protected:
+ uint32_t GetRefreshDriverCount()
+ {
+ return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
+ }
+
+ void StartTimer() override
+ {
+ 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);
+ }
+
+ void StopTimer() override
+ {
+ mTimer->Cancel();
+ }
+
+ void ScheduleNextTick(TimeStamp aNowTime) override
+ {
+ 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 >= GetRefreshDriverCount()) {
+ 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, GetRefreshDriverCount());
+ }
+
+ /* Runs just one driver's tick. */
+ void TickOne()
+ {
+ int64_t jsnow = JS_Now();
+ TimeStamp now = TimeStamp::Now();
+
+ ScheduleNextTick(now);
+
+ mLastFireEpoch = jsnow;
+ mLastFireTime = now;
+ mLastFireSkipped = false;
+
+ nsTArray<RefPtr<nsRefreshDriver> > drivers(mContentRefreshDrivers);
+ drivers.AppendElements(mRootRefreshDrivers);
+ size_t index = mNextDriverIndex;
+
+ if (index < drivers.Length() &&
+ !drivers[index]->IsTestControllingRefreshesEnabled())
+ {
+ TickDriver(drivers[index], jsnow, now);
+ mLastFireSkipped = mLastFireSkipped || drivers[index]->SkippedPaints();
+ }
+
+ mNextDriverIndex++;
+ }
+
+ static void TimerTickOne(nsITimer* aTimer, void* aClosure)
+ {
+ InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
+ timer->TickOne();
+ }
+
+ double mNextTickDuration;
+ double mDisableAfterMilliseconds;
+ uint32_t mNextDriverIndex;
+};
+
+// The PBackground protocol connection callback. It will be called when
+// PBackground is ready. Then we create the PVsync sub-protocol for our
+// vsync-base RefreshTimer.
+class VsyncChildCreateCallback final : public nsIIPCBackgroundChildCreateCallback
+{
+ NS_DECL_ISUPPORTS
+
+public:
+ VsyncChildCreateCallback()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ static void CreateVsyncActor(PBackgroundChild* aPBackgroundChild)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPBackgroundChild);
+
+ layout::PVsyncChild* actor = aPBackgroundChild->SendPVsyncConstructor();
+ layout::VsyncChild* child = static_cast<layout::VsyncChild*>(actor);
+ nsRefreshDriver::PVsyncActorCreated(child);
+ }
+
+private:
+ virtual ~VsyncChildCreateCallback() = default;
+
+ void ActorCreated(PBackgroundChild* aPBackgroundChild) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPBackgroundChild);
+ CreateVsyncActor(aPBackgroundChild);
+ }
+
+ void ActorFailed() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_CRASH("Failed To Create VsyncChild Actor");
+ }
+}; // VsyncChildCreateCallback
+NS_IMPL_ISUPPORTS(VsyncChildCreateCallback, nsIIPCBackgroundChildCreateCallback)
+
+} // namespace mozilla
+
+static RefreshDriverTimer* sRegularRateTimer;
+static InactiveRefreshDriverTimer* sThrottledRateTimer;
+
+#ifdef XP_WIN
+static int32_t sHighPrecisionTimerRequests = 0;
+// a bare pointer to avoid introducing a static constructor
+static nsITimer *sDisableHighPrecisionTimersTimer = nullptr;
+#endif
+
+static void
+CreateContentVsyncRefreshTimer(void*)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ // Create the PVsync actor child for vsync-base refresh timer.
+ // PBackgroundChild is created asynchronously. If PBackgroundChild is still
+ // unavailable, setup VsyncChildCreateCallback callback to handle the async
+ // connect. We will still use software timer before PVsync ready, and change
+ // to use hw timer when the connection is done. Please check
+ // VsyncChildCreateCallback::CreateVsyncActor() and
+ // nsRefreshDriver::PVsyncActorCreated().
+ PBackgroundChild* backgroundChild = BackgroundChild::GetForCurrentThread();
+ if (backgroundChild) {
+ // If we already have PBackgroundChild, create the
+ // child VsyncRefreshDriverTimer here.
+ VsyncChildCreateCallback::CreateVsyncActor(backgroundChild);
+ return;
+ }
+ // Setup VsyncChildCreateCallback callback
+ RefPtr<nsIIPCBackgroundChildCreateCallback> callback = new VsyncChildCreateCallback();
+ if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
+ MOZ_CRASH("PVsync actor create failed!");
+ }
+}
+
+static void
+CreateVsyncRefreshTimer()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PodArrayZero(sJankLevels);
+ // Sometimes, gfxPrefs is not initialized here. Make sure the gfxPrefs is
+ // ready.
+ gfxPrefs::GetSingleton();
+
+ if (gfxPlatform::IsInLayoutAsapMode()) {
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Make sure all vsync systems are ready.
+ gfxPlatform::GetPlatform();
+ // In parent process, we don't need to use ipc. We can create the
+ // VsyncRefreshDriverTimer directly.
+ sRegularRateTimer = new VsyncRefreshDriverTimer();
+ return;
+ }
+
+ // If this process is not created by NUWA, just create the vsync timer here.
+ CreateContentVsyncRefreshTimer(nullptr);
+}
+
+static uint32_t
+GetFirstFrameDelay(imgIRequest* req)
+{
+ nsCOMPtr<imgIContainer> container;
+ if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
+ return 0;
+ }
+
+ // If this image isn't animated, there isn't a first frame delay.
+ int32_t delay = container->GetFirstFrameDelay();
+ if (delay < 0)
+ return 0;
+
+ return static_cast<uint32_t>(delay);
+}
+
+/* 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 / gfxPlatform::GetDefaultFrameRate());
+}
+
+// Compute the interval to use for the refresh driver timer, in milliseconds.
+// outIsDefault indicates that rate was not explicitly set by the user
+// so we might choose other, more appropriate rates (e.g. vsync, etc)
+// layout.frame_rate=0 indicates "ASAP mode".
+// In ASAP mode rendering is iterated as fast as possible (typically for stress testing).
+// A target rate of 10k is used internally instead of special-handling 0.
+// Backends which block on swap/present/etc should try to not block
+// when layout.frame_rate=0 - to comply with "ASAP" as much as possible.
+double
+nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const
+{
+ int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
+ if (rate < 0) {
+ rate = gfxPlatform::GetDefaultFrameRate();
+ if (outIsDefault) {
+ *outIsDefault = true;
+ }
+ } else {
+ if (outIsDefault) {
+ *outIsDefault = false;
+ }
+ }
+
+ if (rate == 0) {
+ rate = 10000;
+ }
+
+ return 1000.0 / rate;
+}
+
+/* static */ double
+nsRefreshDriver::GetThrottledTimerInterval()
+{
+ int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
+ if (rate <= 0) {
+ rate = DEFAULT_THROTTLED_FRAME_RATE;
+ }
+ return 1000.0 / rate;
+}
+
+/* static */ mozilla::TimeDuration
+nsRefreshDriver::GetMinRecomputeVisibilityInterval()
+{
+ int32_t interval =
+ Preferences::GetInt("layout.visibility.min-recompute-interval-ms", -1);
+ if (interval <= 0) {
+ interval = DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS;
+ }
+ return TimeDuration::FromMilliseconds(interval);
+}
+
+/* static */ mozilla::TimeDuration
+nsRefreshDriver::GetMinNotifyIntersectionObserversInterval()
+{
+ int32_t interval =
+ Preferences::GetInt("layout.visibility.min-notify-intersection-observers-interval-ms", -1);
+ if (interval <= 0) {
+ interval = DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS;
+ }
+ return TimeDuration::FromMilliseconds(interval);
+}
+
+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) {
+ bool isDefault = true;
+ double rate = GetRegularTimerInterval(&isDefault);
+
+ // Try to use vsync-base refresh timer first for sRegularRateTimer.
+ CreateVsyncRefreshTimer();
+
+ if (!sRegularRateTimer) {
+ sRegularRateTimer = new StartupRefreshDriverTimer(rate);
+ }
+ }
+ return sRegularRateTimer;
+}
+
+nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
+ : mActiveTimer(nullptr),
+ mReflowCause(nullptr),
+ mStyleCause(nullptr),
+ mPresContext(aPresContext),
+ mRootRefresh(nullptr),
+ mPendingTransaction(0),
+ mCompletedTransaction(0),
+ mFreezeCount(0),
+ mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(
+ GetThrottledTimerInterval())),
+ mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
+ mMinNotifyIntersectionObserversInterval(
+ GetMinNotifyIntersectionObserversInterval()),
+ mThrottled(false),
+ mNeedToRecomputeVisibility(false),
+ mTestControllingRefreshes(false),
+ mViewManagerFlushIsPending(false),
+ mRequestedHighPrecision(false),
+ mInRefresh(false),
+ mWaitingForTransaction(false),
+ mSkippedPaints(false),
+ mResizeSuppressed(false),
+ mWarningThreshold(REFRESH_WAIT_WARNING)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPresContext,
+ "Need a pres context to tell us to call Disconnect() later "
+ "and decrement sRefreshDriverCount.");
+ mMostRecentRefreshEpochTime = JS_Now();
+ mMostRecentRefresh = TimeStamp::Now();
+ mMostRecentTick = mMostRecentRefresh;
+ mNextThrottledFrameRequestTick = mMostRecentTick;
+ mNextRecomputeVisibilityTick = mMostRecentTick;
+ mNextNotifyIntersectionObserversTick = mMostRecentTick;
+
+ ++sRefreshDriverCount;
+}
+
+nsRefreshDriver::~nsRefreshDriver()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(ObserverCount() == 0,
+ "observers should have unregistered");
+ MOZ_ASSERT(!mActiveTimer, "timer should be gone");
+ MOZ_ASSERT(!mPresContext,
+ "Should have called Disconnect() and decremented "
+ "sRefreshDriverCount!");
+
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ mRootRefresh = nullptr;
+ }
+ for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
+ shell->InvalidatePresShellIfHidden();
+ }
+ mPresShellsToInvalidateIfHidden.Clear();
+
+ profiler_free_backtrace(mStyleCause);
+ profiler_free_backtrace(mReflowCause);
+}
+
+// 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;
+ if (mWaitingForTransaction) {
+ // Disable any refresh driver throttling when entering test mode
+ mWaitingForTransaction = false;
+ mSkippedPaints = false;
+ mWarningThreshold = REFRESH_WAIT_WARNING;
+ }
+ }
+
+ mMostRecentRefreshEpochTime += aMilliseconds * 1000;
+ mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds);
+
+ mozilla::dom::AutoNoJSAPI nojsapi;
+ DoTick();
+}
+
+void
+nsRefreshDriver::RestoreNormalRefresh()
+{
+ mTestControllingRefreshes = false;
+ EnsureTimerStarted(eAllowTimeToGoBackwards);
+ mCompletedTransaction = mPendingTransaction;
+}
+
+TimeStamp
+nsRefreshDriver::MostRecentRefresh() const
+{
+ const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
+
+ return mMostRecentRefresh;
+}
+
+int64_t
+nsRefreshDriver::MostRecentRefreshEpochTime() const
+{
+ const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
+
+ return mMostRecentRefreshEpochTime;
+}
+
+bool
+nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ ObserverArray& array = ArrayFor(aFlushType);
+ bool success = array.AppendElement(aObserver) != nullptr;
+ EnsureTimerStarted();
+ return success;
+}
+
+bool
+nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ mozFlushType aFlushType)
+{
+ ObserverArray& array = ArrayFor(aFlushType);
+ return array.RemoveElement(aObserver);
+}
+
+void
+nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+ mPostRefreshObservers.AppendElement(aObserver);
+}
+
+void
+nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+ mPostRefreshObservers.RemoveElement(aObserver);
+}
+
+bool
+nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
+{
+ uint32_t delay = GetFirstFrameDelay(aRequest);
+ if (delay == 0) {
+ if (!mRequests.PutEntry(aRequest)) {
+ return false;
+ }
+ } else {
+ ImageStartData* start = mStartTable.Get(delay);
+ if (!start) {
+ start = new ImageStartData();
+ mStartTable.Put(delay, start);
+ }
+ start->mEntries.PutEntry(aRequest);
+ }
+
+ EnsureTimerStarted();
+
+ return true;
+}
+
+void
+nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
+{
+ // Try to remove from both places, just in case, because we can't tell
+ // whether RemoveEntry() succeeds.
+ mRequests.RemoveEntry(aRequest);
+ uint32_t delay = GetFirstFrameDelay(aRequest);
+ if (delay != 0) {
+ ImageStartData* start = mStartTable.Get(delay);
+ if (start) {
+ start->mEntries.RemoveEntry(aRequest);
+ }
+ }
+}
+
+void
+nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags)
+{
+ if (mTestControllingRefreshes)
+ return;
+
+ // will it already fire, and no other changes needed?
+ if (mActiveTimer && !(aFlags & eForceAdjustTimer))
+ return;
+
+ if (IsFrozen() || !mPresContext) {
+ // If we don't want to start it now, or we've been disconnected.
+ StopTimer();
+ return;
+ }
+
+ if (mPresContext->Document()->IsBeingUsedAsImage()) {
+ // Image documents receive ticks from clients' refresh drivers.
+ // XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until
+ // they receive refresh-driver ticks from their client docs (bug 1107252).
+ nsIURI* uri = mPresContext->Document()->GetDocumentURI();
+ if (!uri || !IsFontTableURI(uri)) {
+ MOZ_ASSERT(!mActiveTimer,
+ "image doc refresh driver should never have its own timer");
+ 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);
+ }
+
+ // When switching from an inactive timer to an active timer, the root
+ // refresh driver is skipped due to being set to the content refresh
+ // driver's timestamp. In case of EnsureTimerStarted is called from
+ // ScheduleViewManagerFlush, we should avoid this behavior to flush
+ // a paint in the same tick on the root refresh driver.
+ if (aFlags & eNeverAdjustTimer) {
+ return;
+ }
+
+ // Since the different timers are sampled at different rates, when switching
+ // timers, the most recent refresh of the new timer may be *before* the
+ // most recent refresh of the old timer. However, the refresh driver time
+ // should not go backwards so we clamp the most recent refresh time.
+ //
+ // The one exception to this is when we are restoring the refresh driver
+ // from test control in which case the time is expected to go backwards
+ // (see bug 1043078).
+ mMostRecentRefresh =
+ aFlags & eAllowTimeToGoBackwards
+ ? mActiveTimer->MostRecentRefresh()
+ : std::max(mActiveTimer->MostRecentRefresh(), mMostRecentRefresh);
+ mMostRecentRefreshEpochTime =
+ aFlags & eAllowTimeToGoBackwards
+ ? mActiveTimer->MostRecentRefreshEpochTime()
+ : std::max(mActiveTimer->MostRecentRefreshEpochTime(),
+ mMostRecentRefreshEpochTime);
+}
+
+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 haveUnthrottledFrameRequestCallbacks =
+ mFrameRequestCallbackDocs.Length() > 0;
+
+ // if the only change that's needed is that we need high precision,
+ // then just set that
+ if (!mThrottled && !mRequestedHighPrecision &&
+ haveUnthrottledFrameRequestCallbacks) {
+ SetHighPrecisionTimersEnabled(true);
+ } else if (mRequestedHighPrecision && !haveUnthrottledFrameRequestCallbacks) {
+ 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 += mPendingEvents.Length();
+ sum += mFrameRequestCallbackDocs.Length();
+ sum += mThrottledFrameRequestCallbackDocs.Length();
+ sum += mViewManagerFlushIsPending;
+ return sum;
+}
+
+uint32_t
+nsRefreshDriver::ImageRequestCount() const
+{
+ uint32_t count = 0;
+ for (auto iter = mStartTable.ConstIter(); !iter.Done(); iter.Next()) {
+ count += iter.UserData()->mEntries.Count();
+ }
+ return count + 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:
+ MOZ_ASSERT(false, "bad flush type");
+ return *static_cast<ObserverArray*>(nullptr);
+ }
+}
+
+/*
+ * nsITimerCallback implementation
+ */
+
+void
+nsRefreshDriver::DoTick()
+{
+ NS_PRECONDITION(!IsFrozen(), "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());
+ }
+}
+
+struct DocumentFrameCallbacks {
+ explicit DocumentFrameCallbacks(nsIDocument* aDocument) :
+ mDocument(aDocument)
+ {}
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsIDocument::FrameRequestCallbackList mCallbacks;
+};
+
+static nsDocShell* GetDocShell(nsPresContext* aPresContext)
+{
+ return static_cast<nsDocShell*>(aPresContext->GetDocShell());
+}
+
+static bool
+HasPendingAnimations(nsIPresShell* aShell)
+{
+ nsIDocument* doc = aShell->GetDocument();
+ if (!doc) {
+ return false;
+ }
+
+ PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
+ return tracker && tracker->HasPendingAnimations();
+}
+
+/**
+ * Return a list of all the child docShells in a given root docShell that are
+ * visible and are recording markers for the profilingTimeline
+ */
+static void GetProfileTimelineSubDocShells(nsDocShell* aRootDocShell,
+ nsTArray<nsDocShell*>& aShells)
+{
+ if (!aRootDocShell) {
+ return;
+ }
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ if (!timelines || timelines->IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = aRootDocShell->GetDocShellEnumerator(
+ nsIDocShellTreeItem::typeAll,
+ nsIDocShell::ENUMERATE_BACKWARDS,
+ getter_AddRefs(enumerator));
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> curItem;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> curSupports;
+ enumerator->GetNext(getter_AddRefs(curSupports));
+ curItem = do_QueryInterface(curSupports);
+
+ if (!curItem || !curItem->GetRecordProfileTimelineMarkers()) {
+ continue;
+ }
+
+ nsDocShell* shell = static_cast<nsDocShell*>(curItem.get());
+ bool isVisible = false;
+ shell->GetVisibility(&isVisible);
+ if (!isVisible) {
+ continue;
+ }
+
+ aShells.AppendElement(shell);
+ }
+}
+
+static void
+TakeFrameRequestCallbacksFrom(nsIDocument* aDocument,
+ nsTArray<DocumentFrameCallbacks>& aTarget)
+{
+ aTarget.AppendElement(aDocument);
+ aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
+}
+
+void
+nsRefreshDriver::DispatchPendingEvents()
+{
+ // Swap out the current pending events
+ nsTArray<PendingEvent> pendingEvents(Move(mPendingEvents));
+ for (PendingEvent& event : pendingEvents) {
+ bool dummy;
+ event.mTarget->DispatchEvent(event.mEvent, &dummy);
+ }
+}
+
+static bool
+CollectDocuments(nsIDocument* aDocument, void* aDocArray)
+{
+ static_cast<nsCOMArray<nsIDocument>*>(aDocArray)->AppendObject(aDocument);
+ aDocument->EnumerateSubDocuments(CollectDocuments, aDocArray);
+ return true;
+}
+
+void
+nsRefreshDriver::DispatchAnimationEvents()
+{
+ if (!mPresContext) {
+ return;
+ }
+
+ nsCOMArray<nsIDocument> documents;
+ CollectDocuments(mPresContext->Document(), &documents);
+
+ for (int32_t i = 0; i < documents.Count(); ++i) {
+ nsIDocument* doc = documents[i];
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ continue;
+ }
+
+ RefPtr<nsPresContext> context = shell->GetPresContext();
+ if (!context || context->RefreshDriver() != this) {
+ continue;
+ }
+
+ context->TransitionManager()->SortEvents();
+ context->AnimationManager()->SortEvents();
+
+ // Dispatch transition events first since transitions conceptually sit
+ // below animations in terms of compositing order.
+ context->TransitionManager()->DispatchEvents();
+ // Check that the presshell has not been destroyed
+ if (context->GetPresShell()) {
+ context->AnimationManager()->DispatchEvents();
+ }
+ }
+}
+
+void
+nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime)
+{
+ // Grab all of our frame request callbacks up front.
+ nsTArray<DocumentFrameCallbacks>
+ frameRequestCallbacks(mFrameRequestCallbackDocs.Length() +
+ mThrottledFrameRequestCallbackDocs.Length());
+
+ // First, grab throttled frame request callbacks.
+ {
+ nsTArray<nsIDocument*> docsToRemove;
+
+ // We always tick throttled frame requests if the entire refresh driver is
+ // throttled, because in that situation throttled frame requests tick at the
+ // same frequency as non-throttled frame requests.
+ bool tickThrottledFrameRequests = mThrottled;
+
+ if (!tickThrottledFrameRequests &&
+ aNowTime >= mNextThrottledFrameRequestTick) {
+ mNextThrottledFrameRequestTick = aNowTime + mThrottledFrameRequestInterval;
+ tickThrottledFrameRequests = true;
+ }
+
+ for (nsIDocument* doc : mThrottledFrameRequestCallbackDocs) {
+ if (tickThrottledFrameRequests) {
+ // We're ticking throttled documents, so grab this document's requests.
+ // We don't bother appending to docsToRemove because we're going to
+ // clear mThrottledFrameRequestCallbackDocs anyway.
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ } else if (!doc->ShouldThrottleFrameRequests()) {
+ // This document is no longer throttled, so grab its requests even
+ // though we're not ticking throttled frame requests right now. If
+ // this is the first unthrottled document with frame requests, we'll
+ // enter high precision mode the next time the callback is scheduled.
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ docsToRemove.AppendElement(doc);
+ }
+ }
+
+ // Remove all the documents we're ticking from
+ // mThrottledFrameRequestCallbackDocs so they can be readded as needed.
+ if (tickThrottledFrameRequests) {
+ mThrottledFrameRequestCallbackDocs.Clear();
+ } else {
+ // XXX(seth): We're using this approach to avoid concurrent modification
+ // of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either
+ // zero elements or a very small number, so this should be OK in practice.
+ for (nsIDocument* doc : docsToRemove) {
+ mThrottledFrameRequestCallbackDocs.RemoveElement(doc);
+ }
+ }
+ }
+
+ // Now grab unthrottled frame request callbacks.
+ for (nsIDocument* doc : mFrameRequestCallbackDocs) {
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ }
+
+ // Reset mFrameRequestCallbackDocs so they can be readded as needed.
+ mFrameRequestCallbackDocs.Clear();
+
+ if (!frameRequestCallbacks.IsEmpty()) {
+ profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START);
+ for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) {
+ // XXXbz Bug 863140: GetInnerWindow can return the outer
+ // window in some cases.
+ nsPIDOMWindowInner* innerWindow =
+ docCallbacks.mDocument->GetInnerWindow();
+ DOMHighResTimeStamp timeStamp = 0;
+ if (innerWindow && innerWindow->IsInnerWindow()) {
+ mozilla::dom::Performance* perf = innerWindow->GetPerformance();
+ if (perf) {
+ timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime);
+ }
+ // else window is partially torn down already
+ }
+ for (auto& callback : docCallbacks.mCallbacks) {
+ callback->Call(timeStamp);
+ }
+ }
+ profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END);
+ }
+}
+
+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;
+ }
+
+ PROFILER_LABEL("nsRefreshDriver", "Tick",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ // 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 (IsFrozen() || !mPresContext) {
+ return;
+ }
+
+ // We can have a race condition where the vsync timestamp
+ // is before the most recent refresh due to a forced refresh.
+ // The underlying assumption is that the refresh driver tick can only
+ // go forward in time, not backwards. To prevent the refresh
+ // driver from going back in time, just skip this tick and
+ // wait until the next tick.
+ if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) {
+ return;
+ }
+
+ TimeStamp previousRefresh = mMostRecentRefresh;
+
+ mMostRecentRefresh = aNowTime;
+ mMostRecentRefreshEpochTime = aNowEpoch;
+
+ if (IsWaitingForPaint(aNowTime)) {
+ // We're currently suspended waiting for earlier Tick's to
+ // be completed (on the Compositor). Mark that we missed the paint
+ // and keep waiting.
+ return;
+ }
+ mMostRecentTick = aNowTime;
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ mRootRefresh = nullptr;
+ }
+ mSkippedPaints = false;
+ mWarningThreshold = 1;
+
+ 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;
+ }
+
+ mResizeSuppressed = false;
+
+ AutoRestore<bool> restoreInRefresh(mInRefresh);
+ mInRefresh = true;
+
+ AutoRestore<TimeStamp> restoreTickStart(mTickStart);
+ mTickStart = TimeStamp::Now();
+
+ gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
+
+ // We want to process any pending APZ metrics ahead of their positions
+ // in the queue. This will prevent us from spending precious time
+ // painting a stale displayport.
+ if (gfxPrefs::APZPeekMessages()) {
+ nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages();
+ }
+
+ /*
+ * 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()) {
+ RefPtr<nsARefreshObserver> obs = etor.GetNext();
+ obs->WillRefresh(aNowTime);
+
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ StopTimer();
+ return;
+ }
+ }
+
+ if (i == 0) {
+ // This is the Flush_Style case.
+
+ DispatchAnimationEvents();
+ DispatchPendingEvents();
+ RunFrameRequestCallbacks(aNowTime);
+
+ if (mPresContext && mPresContext->GetPresShell()) {
+ bool tracingStyleFlush = false;
+ AutoTArray<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;
+
+ if (!tracingStyleFlush) {
+ tracingStyleFlush = true;
+ profiler_tracing("Paint", "Styles", mStyleCause, TRACING_INTERVAL_START);
+ mStyleCause = nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> shellKungFuDeathGrip(shell);
+ mStyleFlushObservers.RemoveElement(shell);
+ RestyleManagerHandle restyleManager =
+ shell->GetPresContext()->RestyleManager();
+ restyleManager->SetObservingRefreshDriver(false);
+ shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
+ // Inform the FontFaceSet that we ticked, so that it can resolve its
+ // ready promise if it needs to (though it might still be waiting on
+ // a layout flush).
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext) {
+ presContext->NotifyFontFaceSetOnRefresh();
+ }
+ mNeedToRecomputeVisibility = true;
+ }
+
+
+ if (tracingStyleFlush) {
+ profiler_tracing("Paint", "Styles", TRACING_INTERVAL_END);
+ }
+ }
+ } else if (i == 1) {
+ // This is the Flush_Layout case.
+ bool tracingLayoutFlush = false;
+ AutoTArray<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;
+
+ if (!tracingLayoutFlush) {
+ tracingLayoutFlush = true;
+ profiler_tracing("Paint", "Reflow", mReflowCause, TRACING_INTERVAL_START);
+ mReflowCause = nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> shellKungFuDeathGrip(shell);
+ mLayoutFlushObservers.RemoveElement(shell);
+ shell->mReflowScheduled = false;
+ shell->mSuppressInterruptibleReflows = false;
+ mozFlushType flushType = HasPendingAnimations(shell)
+ ? Flush_Layout
+ : Flush_InterruptibleLayout;
+ shell->FlushPendingNotifications(ChangesToFlush(flushType, false));
+ // Inform the FontFaceSet that we ticked, so that it can resolve its
+ // ready promise if it needs to.
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext) {
+ presContext->NotifyFontFaceSetOnRefresh();
+ }
+ mNeedToRecomputeVisibility = true;
+ }
+
+ if (tracingLayoutFlush) {
+ profiler_tracing("Paint", "Reflow", TRACING_INTERVAL_END);
+ }
+ }
+
+ // The pres context may be destroyed during we do the flushing.
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ StopTimer();
+ return;
+ }
+ }
+
+ // Recompute approximate frame visibility if it's necessary and enough time
+ // has passed since the last time we did it.
+ if (mNeedToRecomputeVisibility && !mThrottled &&
+ aNowTime >= mNextRecomputeVisibilityTick &&
+ !presShell->IsPaintingSuppressed()) {
+ mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
+ mNeedToRecomputeVisibility = false;
+
+ presShell->ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+
+ bool notifyIntersectionObservers = false;
+ if (aNowTime >= mNextNotifyIntersectionObserversTick) {
+ mNextNotifyIntersectionObserversTick =
+ aNowTime + mMinNotifyIntersectionObserversInterval;
+ notifyIntersectionObservers = true;
+ }
+ nsCOMArray<nsIDocument> documents;
+ CollectDocuments(mPresContext->Document(), &documents);
+ for (int32_t i = 0; i < documents.Count(); ++i) {
+ nsIDocument* doc = documents[i];
+ doc->UpdateIntersectionObservations();
+ if (notifyIntersectionObservers) {
+ doc->ScheduleIntersectionObserverNotification();
+ }
+ }
+
+ /*
+ * Perform notification to imgIRequests subscribed to listen
+ * for refresh events.
+ */
+
+ for (auto iter = mStartTable.Iter(); !iter.Done(); iter.Next()) {
+ const uint32_t& delay = iter.Key();
+ ImageStartData* data = iter.UserData();
+
+ if (data->mStartTime) {
+ TimeStamp& start = *data->mStartTime;
+ TimeDuration prev = previousRefresh - start;
+ TimeDuration curr = aNowTime - start;
+ uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay;
+
+ // We want to trigger images' refresh if we've just crossed over a
+ // multiple of the first image's start time. If so, set the animation
+ // start time to the nearest multiple of the delay and move all the
+ // images in this table to the main requests table.
+ if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) {
+ mozilla::TimeStamp desired =
+ start + TimeDuration::FromMilliseconds(prevMultiple * delay);
+ BeginRefreshingImages(data->mEntries, desired);
+ }
+ } else {
+ // This is the very first time we've drawn images with this time delay.
+ // Set the animation start time to "now" and move all the images in this
+ // table to the main requests table.
+ mozilla::TimeStamp desired = aNowTime;
+ BeginRefreshingImages(data->mEntries, desired);
+ data->mStartTime.emplace(aNowTime);
+ }
+ }
+
+ if (mRequests.Count()) {
+ // RequestRefresh may run scripts, so it's not safe to directly call it
+ // while using a hashtable enumerator to enumerate mRequests in case
+ // script modifies the hashtable. Instead, we build a (local) array of
+ // images to refresh, and then we refresh each image in that array.
+ nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count());
+
+ for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
+ nsISupportsHashKey* entry = iter.Get();
+ auto req = static_cast<imgIRequest*>(entry->GetKey());
+ MOZ_ASSERT(req, "Unable to retrieve the image request");
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
+ imagesToRefresh.AppendElement(image.forget());
+ }
+ }
+
+ for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
+ imagesToRefresh[i]->RequestRefresh(aNowTime);
+ }
+ }
+
+ for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
+ shell->InvalidatePresShellIfHidden();
+ }
+ mPresShellsToInvalidateIfHidden.Clear();
+
+ bool notifyGC = false;
+ if (mViewManagerFlushIsPending) {
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+
+ nsTArray<nsDocShell*> profilingDocShells;
+ GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells);
+ for (nsDocShell* docShell : profilingDocShells) {
+ // For the sake of the profile timeline's simplicity, this is flagged as
+ // paint even if it includes creating display lists
+ MOZ_ASSERT(timelines);
+ MOZ_ASSERT(timelines->HasConsumer(docShell));
+ timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::START);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Starting ProcessPendingUpdates\n");
+ }
+#endif
+
+ mViewManagerFlushIsPending = false;
+ RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
+ {
+ PaintTelemetry::AutoRecordPaint record;
+ vm->ProcessPendingUpdates();
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Ending ProcessPendingUpdates\n");
+ }
+#endif
+
+ for (nsDocShell* docShell : profilingDocShells) {
+ MOZ_ASSERT(timelines);
+ MOZ_ASSERT(timelines->HasConsumer(docShell));
+ timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::END);
+ }
+
+ notifyGC = true;
+ }
+
+#ifndef ANDROID /* bug 1142079 */
+ mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart);
+#endif
+
+ nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers);
+ while (iter.HasMore()) {
+ nsAPostRefreshObserver* observer = iter.GetNext();
+ observer->DidRefresh();
+ }
+
+ ConfigureHighPrecision();
+
+ NS_ASSERTION(mInRefresh, "Still in refresh");
+
+ if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
+ ScheduleViewManagerFlush();
+ }
+
+ if (notifyGC && nsContentUtils::XPConnect()) {
+ nsContentUtils::XPConnect()->NotifyDidPaint();
+ nsJSContext::NotifyDidPaint();
+ }
+}
+
+void
+nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
+ mozilla::TimeStamp aDesired)
+{
+ for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
+ auto req = static_cast<imgIRequest*>(iter.Get()->GetKey());
+ MOZ_ASSERT(req, "Unable to retrieve the image request");
+
+ mRequests.PutEntry(req);
+
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
+ image->SetAnimationStartTime(aDesired);
+ }
+ }
+ aEntries.Clear();
+}
+
+void
+nsRefreshDriver::Freeze()
+{
+ StopTimer();
+ mFreezeCount++;
+}
+
+void
+nsRefreshDriver::Thaw()
+{
+ NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
+
+ if (mFreezeCount > 0) {
+ mFreezeCount--;
+ }
+
+ if (mFreezeCount == 0) {
+ 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(NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
+ EnsureTimerStarted();
+ }
+ }
+}
+
+void
+nsRefreshDriver::FinishedWaitingForTransaction()
+{
+ mWaitingForTransaction = false;
+ if (mSkippedPaints &&
+ !IsInRefresh() &&
+ (ObserverCount() || ImageRequestCount())) {
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
+ DoRefresh();
+ profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
+ }
+ mSkippedPaints = false;
+ mWarningThreshold = 1;
+}
+
+uint64_t
+nsRefreshDriver::GetTransactionId()
+{
+ ++mPendingTransaction;
+
+ if (mPendingTransaction >= mCompletedTransaction + 2 &&
+ !mWaitingForTransaction &&
+ !mTestControllingRefreshes) {
+ mWaitingForTransaction = true;
+ mSkippedPaints = false;
+ mWarningThreshold = 1;
+ }
+
+ return mPendingTransaction;
+}
+
+uint64_t
+nsRefreshDriver::LastTransactionId() const
+{
+ return mPendingTransaction;
+}
+
+void
+nsRefreshDriver::RevokeTransactionId(uint64_t aTransactionId)
+{
+ MOZ_ASSERT(aTransactionId == mPendingTransaction);
+ if (mPendingTransaction == mCompletedTransaction + 2 &&
+ mWaitingForTransaction) {
+ MOZ_ASSERT(!mSkippedPaints, "How did we skip a paint when we're in the middle of one?");
+ FinishedWaitingForTransaction();
+ }
+ mPendingTransaction--;
+}
+
+mozilla::TimeStamp
+nsRefreshDriver::GetTransactionStart()
+{
+ return mTickStart;
+}
+
+void
+nsRefreshDriver::NotifyTransactionCompleted(uint64_t aTransactionId)
+{
+ if (aTransactionId > mCompletedTransaction) {
+ if (mPendingTransaction > mCompletedTransaction + 1 &&
+ mWaitingForTransaction) {
+ mCompletedTransaction = aTransactionId;
+ FinishedWaitingForTransaction();
+ } else {
+ mCompletedTransaction = aTransactionId;
+ }
+ }
+}
+
+void
+nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime)
+{
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ mRootRefresh = nullptr;
+ if (mSkippedPaints) {
+ DoRefresh();
+ }
+}
+
+bool
+nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime)
+{
+ if (mTestControllingRefreshes) {
+ return false;
+ }
+
+ if (mWaitingForTransaction) {
+ if (mSkippedPaints && aTime > (mMostRecentTick + TimeDuration::FromMilliseconds(mWarningThreshold * 1000))) {
+ // XXX - Bug 1303369 - too many false positives.
+ //gfxCriticalNote << "Refresh driver waiting for the compositor for "
+ // << (aTime - mMostRecentTick).ToSeconds()
+ // << " seconds.";
+ mWarningThreshold *= 2;
+ }
+
+ mSkippedPaints = true;
+ return true;
+ }
+
+ // Try find the 'root' refresh driver for the current window and check
+ // if that is waiting for a paint.
+ nsPresContext* pc = GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ if (rootContext) {
+ nsRefreshDriver *rootRefresh = rootContext->RefreshDriver();
+ if (rootRefresh && rootRefresh != this) {
+ if (rootRefresh->IsWaitingForPaint(aTime)) {
+ if (mRootRefresh != rootRefresh) {
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
+ }
+ rootRefresh->AddRefreshObserver(this, Flush_Style);
+ mRootRefresh = rootRefresh;
+ }
+ mSkippedPaints = true;
+ return true;
+ }
+ }
+ }
+ return 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(eForceAdjustTimer);
+ }
+ }
+}
+
+/*static*/ void
+nsRefreshDriver::PVsyncActorCreated(VsyncChild* aVsyncChild)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ auto* vsyncRefreshDriverTimer =
+ new VsyncRefreshDriverTimer(aVsyncChild);
+
+ // If we are using software timer, swap current timer to
+ // VsyncRefreshDriverTimer.
+ if (sRegularRateTimer) {
+ sRegularRateTimer->SwapRefreshDrivers(vsyncRefreshDriverTimer);
+ delete sRegularRateTimer;
+ }
+ sRegularRateTimer = vsyncRefreshDriverTimer;
+}
+
+void
+nsRefreshDriver::DoRefresh()
+{
+ // Don't do a refresh unless we're in a state where we should be refreshing.
+ if (!IsFrozen() && 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(eNeverAdjustTimer);
+}
+
+void
+nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
+{
+ NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
+ mFrameRequestCallbackDocs.NoIndex &&
+ mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) ==
+ mThrottledFrameRequestCallbackDocs.NoIndex,
+ "Don't schedule the same document multiple times");
+ if (aDocument->ShouldThrottleFrameRequests()) {
+ mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);
+ } else {
+ mFrameRequestCallbackDocs.AppendElement(aDocument);
+ }
+
+ // make sure that the timer is running
+ ConfigureHighPrecision();
+ EnsureTimerStarted();
+}
+
+void
+nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
+{
+ mFrameRequestCallbackDocs.RemoveElement(aDocument);
+ mThrottledFrameRequestCallbackDocs.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.
+}
+
+void
+nsRefreshDriver::ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent)
+{
+ mPendingEvents.AppendElement(PendingEvent{aTarget, aEvent});
+ // make sure that the timer is running
+ EnsureTimerStarted();
+}
+
+void
+nsRefreshDriver::CancelPendingEvents(nsIDocument* aDocument)
+{
+ for (auto i : Reversed(MakeRange(mPendingEvents.Length()))) {
+ if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) {
+ mPendingEvents.RemoveElementAt(i);
+ }
+ }
+}
+
+/* static */ Maybe<TimeStamp>
+nsRefreshDriver::GetIdleDeadlineHint()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sRegularRateTimer) {
+ return Nothing();
+ }
+
+ // For computing idleness of refresh drivers we only care about
+ // sRegularRateTimer, since we consider refresh drivers attached to
+ // sThrottledRateTimer to be inactive. This implies that tasks
+ // resulting from a tick on the sRegularRateTimer counts as being
+ // busy but tasks resulting from a tick on sThrottledRateTimer
+ // counts as being idle.
+ return sRegularRateTimer->GetIdleDeadlineHint();
+}
+
+void
+nsRefreshDriver::Disconnect()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StopTimer();
+
+ if (mPresContext) {
+ mPresContext = nullptr;
+ if (--sRefreshDriverCount == 0) {
+ Shutdown();
+ }
+ }
+}
+
+/* static */ bool
+nsRefreshDriver::IsJankCritical()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return sActiveVsyncTimers > 0;
+}
+
+/* static */ bool
+nsRefreshDriver::GetJankLevels(Vector<uint64_t>& aJank) {
+ aJank.clear();
+ return aJank.append(sJankLevels, ArrayLength(sJankLevels));
+}
+
+#undef LOG