Backed out changeset ff33e6c6f6a5 (bug 1367905) for memory leaks
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 07 Jun 2017 15:13:37 +0200
changeset 410802 1cca42e690aeed06b38e20d7bd22295e2b4e532b
parent 410801 c154fc4eefbab2ca95c55768a631620ac6023ac7
child 410803 bad37b9f58eb9b2f4156ff35c27f4755cdedfae9
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1367905
milestone55.0a1
backs outff33e6c6f6a55f68d48af4b94b43ae1f54f56ce1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset ff33e6c6f6a5 (bug 1367905) for memory leaks
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
layout/tools/reftest/reftest-content.js
modules/libpref/init/all.js
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -51,26 +51,24 @@
 #include "nsIArray.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "prmem.h"
 #include "WrapperFactory.h"
 #include "nsGlobalWindow.h"
 #include "nsScriptNameSpaceManager.h"
 #include "mozilla/AutoRestore.h"
-#include "mozilla/MainThreadIdlePeriod.h"
-#include "mozilla/StaticPtr.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsAXPCNativeCallContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/SystemGroup.h"
-#include "nsRefreshDriver.h"
+
 #include "nsJSPrincipals.h"
 
 #ifdef XP_MACOSX
 // AssertMacros.h defines 'check' and conflicts with AccessCheck.h
 #undef check
 #endif
 #include "AccessCheck.h"
 
@@ -108,34 +106,31 @@ const size_t gStackSize = 8192;
 
 // The default amount of time to wait from the user being idle to starting a
 // shrinking GC.
 #define NS_DEAULT_INACTIVE_GC_DELAY 300000 // ms
 
 // Maximum amount of time that should elapse between incremental GC slices
 #define NS_INTERSLICE_GC_DELAY      100 // ms
 
+// If we haven't painted in 100ms, or we're in e10s parent process and
+// user isn't active, we allow for a longer GC budget.
+#define NS_INTERSLICE_GC_BUDGET     40 // ms
+
 // The amount of time we wait between a request to CC (after GC ran)
 // and doing the actual CC.
 #define NS_CC_DELAY                 6000 // ms
 
 #define NS_CC_SKIPPABLE_DELAY       250 // ms
 
-// ForgetSkippable is usually fast, so we can use small budgets.
-// This isn't a real budget but a hint to CollectorRunner whether there
-// is enough time to call ForgetSkippable.
-static const int64_t kForgetSkippableSliceDuration = 2;
-
 // Maximum amount of time that should elapse between incremental CC slices
 static const int64_t kICCIntersliceDelay = 32; // ms
 
-// Time budget for an incremental CC slice when using timer to run it.
+// Time budget for an incremental CC slice
 static const int64_t kICCSliceBudget = 5; // ms
-// Minimum budget for an incremental CC slice when using idle time to run it.
-static const int64_t kIdleICCSliceBudget = 3; // ms
 
 // Maximum total duration for an ICC
 static const uint32_t kMaxICCDuration = 2000; // ms
 
 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
 // objects in the purple buffer.
 #define NS_CC_FORCED                (2 * 60 * PR_USEC_PER_SEC) // 2 min
 #define NS_CC_FORCED_PURPLE_LIMIT   10
@@ -144,26 +139,24 @@ static const uint32_t kMaxICCDuration = 
 #define NS_MAX_CC_LOCKEDOUT_TIME    (30 * PR_USEC_PER_SEC) // 30 seconds
 
 // Trigger a CC if the purple buffer exceeds this size when we check it.
 #define NS_CC_PURPLE_LIMIT          200
 
 // Large value used to specify that a script should run essentially forever
 #define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
 
-class CollectorRunner;
-
 // if you add statics here, add them to the list in StartupJSEnvironment
 
 static nsITimer *sGCTimer;
 static nsITimer *sShrinkingGCTimer;
-static StaticRefPtr<CollectorRunner> sCCRunner;
-static StaticRefPtr<CollectorRunner> sICCRunner;
+static nsITimer *sCCTimer;
+static nsITimer *sICCTimer;
 static nsITimer *sFullGCTimer;
-static StaticRefPtr<CollectorRunner> sInterSliceGCRunner;
+static nsITimer *sInterSliceGCTimer;
 
 static TimeStamp sLastCCEndTime;
 
 static bool sCCLockedOut;
 static PRTime sCCLockedOutTime;
 
 static JS::GCSliceCallback sPrevGCSliceCallback;
 
@@ -178,28 +171,29 @@ static bool sHasRunGC;
 static uint32_t sPendingLoadCount;
 static bool sLoadingInProgress;
 
 static uint32_t sCCollectedWaitingForGC;
 static uint32_t sCCollectedZonesWaitingForGC;
 static uint32_t sLikelyShortLivingObjectsNeedingGC;
 static bool sPostGCEventsToConsole;
 static bool sPostGCEventsToObserver;
-static int32_t sCCRunnerFireCount = 0;
+static int32_t sCCTimerFireCount = 0;
 static uint32_t sMinForgetSkippableTime = UINT32_MAX;
 static uint32_t sMaxForgetSkippableTime = 0;
 static uint32_t sTotalForgetSkippableTime = 0;
 static uint32_t sRemovedPurples = 0;
 static uint32_t sForgetSkippableBeforeCC = 0;
 static uint32_t sPreviousSuspectedCount = 0;
 static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
 static bool sNeedsFullCC = false;
 static bool sNeedsFullGC = false;
 static bool sNeedsGCAfterCC = false;
 static bool sIncrementalCC = false;
+static bool sDidPaintAfterPreviousICCSlice = false;
 static int32_t sActiveIntersliceGCBudget = 0; // ms;
 static nsScriptNameSpaceManager *gNameSpaceManager;
 
 static PRTime sFirstCollectionTime;
 
 static JSContext* sContext;
 
 static bool sIsInitialized;
@@ -224,164 +218,16 @@ static uint32_t sCompactOnUserInactiveDe
 static bool sIsCompactingOnUserInactive = false;
 
 // In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more
 // aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps
 // us from triggering expensive full collections too frequently.
 static int32_t sExpensiveCollectorPokes = 0;
 static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5;
 
-// Return true if some meaningful work was done.
-typedef bool (*CollectorRunnerCallback) (TimeStamp aDeadline, void* aData);
-
-// Repeating callback runner for CC and GC.
-class CollectorRunner final : public IdleRunnable
-{
-public:
-  static already_AddRefed<CollectorRunner>
-  Create(CollectorRunnerCallback aCallback, uint32_t aDelay,
-         int64_t aBudget, bool aRepeating, void* aData = nullptr)
-  {
-    RefPtr<CollectorRunner> runner =
-      new CollectorRunner(aCallback, aDelay, aBudget, aRepeating, aData);
-    runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
-    return runner.forget();
-  }
-
-  NS_IMETHOD Run() override
-  {
-    if (!mCallback) {
-      return NS_OK;
-    }
-
-    // Deadline is null when called from timer.
-    bool deadLineWasNull = mDeadline.IsNull();
-    bool didRun = false;
-    if (deadLineWasNull || ((TimeStamp::Now() + mBudget) < mDeadline)) {
-      CancelTimer();
-      didRun = mCallback(mDeadline, mData);
-    }
-
-    if (mCallback && (mRepeating || !didRun)) {
-      // If we didn't do meaningful work, don't schedule using immediate
-      // idle dispatch, since that could lead to a loop until the idle
-      // period ends.
-      Schedule(didRun);
-    }
-
-    return NS_OK;
-  }
-
-  static void
-  TimedOut(nsITimer* aTimer, void* aClosure)
-  {
-    RefPtr<CollectorRunner> runnable = static_cast<CollectorRunner*>(aClosure);
-    runnable->Run();
-  }
-
-  void SetDeadline(mozilla::TimeStamp aDeadline) override
-  {
-    mDeadline = aDeadline;
-  };
-
-  void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override
-  {
-    if (mTimerActive) {
-      return;
-    }
-
-    mTarget = aTarget;
-    if (!mTimer) {
-      mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
-    } else {
-      mTimer->Cancel();
-    }
-
-    if (mTimer) {
-      mTimer->SetTarget(mTarget);
-      mTimer->InitWithFuncCallback(TimedOut, this, aDelay,
-                                   nsITimer::TYPE_ONE_SHOT);
-      mTimerActive = true;
-    }
-  }
-
-  nsresult Cancel() override
-  {
-    CancelTimer();
-    mCallback = nullptr;
-    return NS_OK;
-  }
-
-  void Schedule(bool aAllowIdleDispatch)
-  {
-    if (!mCallback) {
-      return;
-    }
-
-    mDeadline = TimeStamp();
-    TimeStamp now = TimeStamp::Now();
-    TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now);
-    if (hint != now) {
-      // RefreshDriver is ticking, let it schedule the idle dispatch.
-      nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay);
-      // Ensure we get called at some point, even if RefreshDriver is stopped.
-      SetTimer(mDelay, mTarget);
-    } else {
-      // RefreshDriver doesn't seem to be running.
-      if (aAllowIdleDispatch) {
-        nsCOMPtr<nsIRunnable> runnable = this;
-        NS_IdleDispatchToCurrentThread(runnable.forget(), mDelay);
-      } else {
-        nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
-        if (thread) {
-          // We weren't allowed to do idle dispatch immediately, do it after a
-          // short timeout.
-          nsCOMPtr<nsIRunnable> runnable =
-            NewRunnableMethod<bool>(this, &CollectorRunner::Schedule, true);
-          thread->DelayedDispatch(runnable.forget(), 16);
-        }
-      }
-    }
-  }
-
-private:
-  explicit CollectorRunner(CollectorRunnerCallback aCallback,
-                           uint32_t aDelay, int64_t aBudget,
-                           bool aRepeating, void* aData)
-    : mCallback(aCallback), mDelay(aDelay)
-    , mBudget(TimeDuration::FromMilliseconds(aBudget))
-    , mRepeating(aRepeating), mTimerActive(false), mData(aData)
-  {
-  }
-
-  ~CollectorRunner()
-  {
-    CancelTimer();
-  }
-
-  void CancelTimer()
-  {
-    nsRefreshDriver::CancelIdleRunnable(this);
-    if (mTimer) {
-      mTimer->Cancel();
-    }
-    mTimerActive = false;
-  }
-
-  nsCOMPtr<nsITimer> mTimer;
-  nsCOMPtr<nsIEventTarget> mTarget;
-  CollectorRunnerCallback mCallback;
-  uint32_t mDelay;
-  TimeStamp mDeadline;
-  TimeDuration mBudget;
-  bool mRepeating;
-  bool mTimerActive;
-  void* mData;
-};
-
 static const char*
 ProcessNameForCollectorLog()
 {
   return XRE_GetProcessType() == GeckoProcessType_Default ?
     "default" : "content";
 }
 
 namespace xpc {
@@ -448,20 +294,20 @@ GetCollectionTimeDelta()
   return 0;
 }
 
 static void
 KillTimers()
 {
   nsJSContext::KillGCTimer();
   nsJSContext::KillShrinkingGCTimer();
-  nsJSContext::KillCCRunner();
-  nsJSContext::KillICCRunner();
+  nsJSContext::KillCCTimer();
+  nsJSContext::KillICCTimer();
   nsJSContext::KillFullGCTimer();
-  nsJSContext::KillInterSliceGCRunner();
+  nsJSContext::KillInterSliceGCTimer();
 }
 
 // If we collected a substantial amount of cycles, poke the GC since more objects
 // might be unreachable now.
 static bool
 NeedsGCAfterCC()
 {
   return sCCollectedWaitingForGC > 250 ||
@@ -1600,66 +1446,59 @@ nsJSContext::CycleCollectNow(nsICycleCol
 
   gCCStats.PrepareForCycleCollectionSlice(aExtraForgetSkippableCalls);
   nsCycleCollector_collect(aListener);
   gCCStats.FinishCycleCollectionSlice();
 }
 
 //static
 void
-nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline)
+nsJSContext::RunCycleCollectorSlice()
 {
   if (!NS_IsMainThread()) {
     return;
   }
 
   PROFILER_LABEL("nsJSContext", "RunCycleCollectorSlice",
     js::ProfileEntry::Category::CC);
 
   gCCStats.PrepareForCycleCollectionSlice();
 
   // Decide how long we want to budget for this slice. By default,
   // use an unlimited budget.
   js::SliceBudget budget = js::SliceBudget::unlimited();
 
   if (sIncrementalCC) {
-    int64_t baseBudget = kICCSliceBudget;
-    if (!aDeadline.IsNull()) {
-      baseBudget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
-    }
-
     if (gCCStats.mBeginTime.IsNull()) {
       // If no CC is in progress, use the standard slice time.
-      budget = js::SliceBudget(js::TimeBudget(baseBudget));
+      budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget));
     } else {
       TimeStamp now = TimeStamp::Now();
 
       // Only run a limited slice if we're within the max running time.
       uint32_t runningTime = TimeBetween(gCCStats.mBeginTime, now);
       if (runningTime < kMaxICCDuration) {
-        const float maxSlice = MainThreadIdlePeriod::GetLongIdlePeriod();
-
         // Try to make up for a delay in running this slice.
-        float sliceDelayMultiplier =
-          TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay;
-        float delaySliceBudget =
-          std::min(baseBudget * sliceDelayMultiplier, maxSlice);
-
-        // Increase slice budgets up to |maxSlice| as we approach
+        float sliceDelayMultiplier = TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay;
+        float delaySliceBudget = kICCSliceBudget * sliceDelayMultiplier;
+
+        // Increase slice budgets up to |maxLaterSlice| as we approach
         // half way through the ICC, to avoid large sync CCs.
         float percentToHalfDone = std::min(2.0f * runningTime / kMaxICCDuration, 1.0f);
-        float laterSliceBudget = maxSlice * percentToHalfDone;
+        const float maxLaterSlice = 40.0f;
+        float laterSliceBudget = maxLaterSlice * percentToHalfDone;
 
         budget = js::SliceBudget(js::TimeBudget(std::max({delaySliceBudget,
-                  laterSliceBudget, (float)baseBudget})));
+                  laterSliceBudget, (float)kICCSliceBudget})));
       }
     }
   }
 
-  nsCycleCollector_collectSlice(budget);
+  nsCycleCollector_collectSlice(budget, sDidPaintAfterPreviousICCSlice);
+  sDidPaintAfterPreviousICCSlice = false;
 
   gCCStats.FinishCycleCollectionSlice();
 }
 
 //static
 void
 nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget)
 {
@@ -1685,71 +1524,76 @@ nsJSContext::ClearMaxCCSliceTime()
 }
 
 uint32_t
 nsJSContext::GetMaxCCSliceTimeSinceClear()
 {
   return gCCStats.mMaxSliceTimeSinceClear;
 }
 
-static bool
-ICCRunnerFired(TimeStamp aDeadline, void* aData)
+static void
+ICCTimerFired(nsITimer* aTimer, void* aClosure)
 {
   if (sDidShutdown) {
-    return false;
+    return;
   }
 
   // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
   // to synchronously finish the GC, which is bad.
 
   if (sCCLockedOut) {
     PRTime now = PR_Now();
     if (sCCLockedOutTime == 0) {
       sCCLockedOutTime = now;
-      return false;
+      return;
     }
     if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
-      return false;
+      return;
     }
   }
 
-  nsJSContext::RunCycleCollectorSlice(aDeadline);
-  return true;
+  nsJSContext::RunCycleCollectorSlice();
 }
 
 //static
 void
 nsJSContext::BeginCycleCollectionCallback()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime;
   gCCStats.mSuspected = nsCycleCollector_suspectedCount();
 
-  KillCCRunner();
+  KillCCTimer();
 
   gCCStats.RunForgetSkippable();
 
-  MOZ_ASSERT(!sICCRunner, "Tried to create a new ICC timer when one already existed.");
+  MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed.");
 
   // Create an ICC timer even if ICC is globally disabled, because we could be manually triggering
   // an incremental collection, and we want to be sure to finish it.
-  sICCRunner = CollectorRunner::Create(ICCRunnerFired, kICCIntersliceDelay,
-                                       kIdleICCSliceBudget, true);
+  CallCreateInstance("@mozilla.org/timer;1", &sICCTimer);
+  if (sICCTimer) {
+    sICCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+    sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+                                         kICCIntersliceDelay,
+                                         nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
+                                         "ICCTimerFired");
+  }
 }
 
 static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
 
 //static
 void
 nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsJSContext::KillICCRunner();
+  nsJSContext::KillICCTimer();
 
   // Update timing information for the current slice before we log it, if
   // we previously called PrepareForCycleCollectionSlice(). During shutdown
   // CCs, this won't happen.
   gCCStats.FinishCycleCollectionSlice();
 
   sCCollectedWaitingForGC += aResults.mFreedGCed;
   sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
@@ -1887,47 +1731,36 @@ nsJSContext::EndCycleCollectionCallback(
   sRemovedPurples = 0;
   sForgetSkippableBeforeCC = 0;
   sNeedsFullCC = false;
   sNeedsGCAfterCC = false;
   gCCStats.Clear();
 }
 
 // static
-bool
-InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData)
+void
+InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
 {
-  nsJSContext::KillInterSliceGCRunner();
-  MOZ_ASSERT(sActiveIntersliceGCBudget > 0);
-  int64_t budget = sActiveIntersliceGCBudget;
-  if (!aDeadline.IsNull()) {
-    budget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
-  }
-
-  uintptr_t reason = reinterpret_cast<uintptr_t>(aData);
-  nsJSContext::GarbageCollectNow(aData ?
-                                   static_cast<JS::gcreason::Reason>(reason) :
-                                   JS::gcreason::INTER_SLICE_GC,
+  nsJSContext::KillInterSliceGCTimer();
+  int64_t budget = XRE_IsE10sParentProcess() && nsContentUtils::GetUserIsInteracting() && sActiveIntersliceGCBudget ?
+    sActiveIntersliceGCBudget : NS_INTERSLICE_GC_BUDGET;
+  nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
                                  nsJSContext::IncrementalGC,
                                  nsJSContext::NonShrinkingGC,
                                  budget);
-  return true;
 }
 
 // static
 void
 GCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   nsJSContext::KillGCTimer();
-  // Now start the actual GC after initial timer has fired.
-  sInterSliceGCRunner = CollectorRunner::Create(InterSliceGCRunnerFired,
-                                                NS_INTERSLICE_GC_DELAY,
-                                                sActiveIntersliceGCBudget,
-                                                false,
-                                                aClosure);
+  uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
+  nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
+                                 nsJSContext::IncrementalGC);
 }
 
 // static
 void
 ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure)
 {
   nsJSContext::KillShrinkingGCTimer();
   sIsCompactingOnUserInactive = true;
@@ -1940,88 +1773,81 @@ static bool
 ShouldTriggerCC(uint32_t aSuspected)
 {
   return sNeedsFullCC ||
          aSuspected > NS_CC_PURPLE_LIMIT ||
          (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
           TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
 }
 
-static bool
-CCRunnerFired(TimeStamp aDeadline, void* aData)
+static void
+CCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   if (sDidShutdown) {
-    return false;
+    return;
   }
 
   static uint32_t ccDelay = NS_CC_DELAY;
   if (sCCLockedOut) {
     ccDelay = NS_CC_DELAY / 3;
 
     PRTime now = PR_Now();
     if (sCCLockedOutTime == 0) {
-      // Reset sCCRunnerFireCount so that we run forgetSkippable
+      // Reset sCCTimerFireCount so that we run forgetSkippable
       // often enough before CC. Because of reduced ccDelay
       // forgetSkippable will be called just a few times.
       // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
       // forgetSkippable and CycleCollectNow eventually.
-      sCCRunnerFireCount = 0;
+      sCCTimerFireCount = 0;
       sCCLockedOutTime = now;
-      return false;
+      return;
     }
     if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
-      return false;
+      return;
     }
   }
 
-  ++sCCRunnerFireCount;
-
-  bool didDoWork = false;
+  ++sCCTimerFireCount;
 
   // During early timer fires, we only run forgetSkippable. During the first
   // late timer fire, we decide if we are going to have a second and final
   // late timer fire, where we may begin to run the CC. Should run at least one
   // early timer fire to allow cleanup before the CC.
   int32_t numEarlyTimerFires = std::max((int32_t)ccDelay / NS_CC_SKIPPABLE_DELAY - 2, 1);
-  bool isLateTimerFire = sCCRunnerFireCount > numEarlyTimerFires;
+  bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires;
   uint32_t suspected = nsCycleCollector_suspectedCount();
   if (isLateTimerFire && ShouldTriggerCC(suspected)) {
-    if (sCCRunnerFireCount == numEarlyTimerFires + 1) {
+    if (sCCTimerFireCount == numEarlyTimerFires + 1) {
       FireForgetSkippable(suspected, true);
-      didDoWork = true;
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         // Our efforts to avoid a CC have failed, so we return to let the
         // timer fire once more to trigger a CC.
-        return didDoWork;
+        return;
       }
     } else {
       // We are in the final timer fire and still meet the conditions for
       // triggering a CC. Let RunCycleCollectorSlice finish the current IGC, if
       // any because that will allow us to include the GC time in the CC pause.
-      nsJSContext::RunCycleCollectorSlice(aDeadline);
-      didDoWork = true;
+      nsJSContext::RunCycleCollectorSlice();
     }
   } else if (((sPreviousSuspectedCount + 100) <= suspected) ||
              (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS)) {
       // Only do a forget skippable if there are more than a few new objects
       // or we're doing the initial forget skippables.
       FireForgetSkippable(suspected, false);
-      didDoWork = true;
   }
 
   if (isLateTimerFire) {
     ccDelay = NS_CC_DELAY;
 
     // We have either just run the CC or decided we don't want to run the CC
     // next time, so kill the timer.
     sPreviousSuspectedCount = 0;
-    nsJSContext::KillCCRunner();
+    nsJSContext::KillCCTimer();
   }
-
-  return didDoWork;
 }
 
 // static
 uint32_t
 nsJSContext::CleanupsSinceLastGC()
 {
   return sCleanupsSinceLastGC;
 }
@@ -2058,23 +1884,23 @@ ReadyToTriggerExpensiveCollectorTimer()
   bool ready = kPokesBetweenExpensiveCollectorTriggers < ++sExpensiveCollectorPokes;
   if (ready) {
     sExpensiveCollectorPokes = 0;
   }
   return ready;
 }
 
 
-// Check all of the various collector timers/runners and see if they are waiting to fire.
-// For the synchronous collector timers/runners, sGCTimer and sCCRunner, we only want to
-// trigger the collection occasionally, because they are expensive.  The incremental collector
-// timers, sInterSliceGCRunner and sICCRunner, are fast and need to be run many times, so
+// Check all of the various collector timers and see if they are waiting to fire.
+// For the synchronous collector timers, sGCTimer and sCCTimer, we only want to trigger
+// the collection occasionally, because they are expensive.  The incremental collector
+// timers, sInterSliceGCTimer and sICCTimer, are fast and need to be run many times, so
 // always run their corresponding timer.
 
-// This does not check sFullGCTimer, as that's a more expensive collection we run
+// This does not check sFullGCTimer, as that's an even more expensive collection we run
 // on a long timer.
 
 // static
 void
 nsJSContext::RunNextCollectorTimer()
 {
   if (sShuttingDown) {
     return;
@@ -2082,34 +1908,34 @@ nsJSContext::RunNextCollectorTimer()
 
   if (sGCTimer) {
     if (ReadyToTriggerExpensiveCollectorTimer()) {
       GCTimerFired(nullptr, reinterpret_cast<void *>(JS::gcreason::DOM_WINDOW_UTILS));
     }
     return;
   }
 
-  if (sInterSliceGCRunner) {
-    InterSliceGCRunnerFired(TimeStamp(), nullptr);
+  if (sInterSliceGCTimer) {
+    InterSliceGCTimerFired(nullptr, nullptr);
     return;
   }
 
   // Check the CC timers after the GC timers, because the CC timers won't do
   // anything if a GC is in progress.
   MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out.");
 
-  if (sCCRunner) {
+  if (sCCTimer) {
     if (ReadyToTriggerExpensiveCollectorTimer()) {
-      CCRunnerFired(TimeStamp(), nullptr);
+      CCTimerFired(nullptr, nullptr);
     }
     return;
   }
 
-  if (sICCRunner) {
-    ICCRunnerFired(TimeStamp(), nullptr);
+  if (sICCTimer) {
+    ICCTimerFired(nullptr, nullptr);
     return;
   }
 }
 
 // static
 void
 nsJSContext::PokeGC(JS::gcreason::Reason aReason,
                     JSObject* aObj,
@@ -2121,30 +1947,30 @@ nsJSContext::PokeGC(JS::gcreason::Reason
 
   if (aObj) {
     JS::Zone* zone = JS::GetObjectZone(aObj);
     CycleCollectedJSRuntime::Get()->AddZoneWaitingForGC(zone);
   } else if (aReason != JS::gcreason::CC_WAITING) {
     sNeedsFullGC = true;
   }
 
-  if (sGCTimer || sInterSliceGCRunner) {
+  if (sGCTimer || sInterSliceGCTimer) {
     // There's already a timer for GC'ing, just return
     return;
   }
 
-  if (sCCRunner) {
+  if (sCCTimer) {
     // Make sure CC is called...
     sNeedsFullCC = true;
     // and GC after it.
     sNeedsGCAfterCC = true;
     return;
   }
 
-  if (sICCRunner) {
+  if (sICCTimer) {
     // Make sure GC is called after the current CC completes.
     // No need to set sNeedsFullCC because we are currently running a CC.
     sNeedsGCAfterCC = true;
     return;
   }
 
   CallCreateInstance("@mozilla.org/timer;1", &sGCTimer);
 
@@ -2160,17 +1986,16 @@ nsJSContext::PokeGC(JS::gcreason::Reason
                                       reinterpret_cast<void *>(aReason),
                                       aDelay
                                       ? aDelay
                                       : (first
                                          ? NS_FIRST_GC_DELAY
                                          : NS_GC_DELAY),
                                       nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
                                       "GCTimerFired");
-
   first = false;
 }
 
 // static
 void
 nsJSContext::PokeShrinkingGC()
 {
   if (sShrinkingGCTimer || sShuttingDown) {
@@ -2190,29 +2015,34 @@ nsJSContext::PokeShrinkingGC()
                                                nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
                                                "ShrinkingGCTimerFired");
 }
 
 // static
 void
 nsJSContext::MaybePokeCC()
 {
-  if (sCCRunner || sICCRunner || sShuttingDown || !sHasRunGC) {
+  if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) {
     return;
   }
 
   if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
-    sCCRunnerFireCount = 0;
-
+    sCCTimerFireCount = 0;
+    CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
+    if (!sCCTimer) {
+      return;
+    }
     // We can kill some objects before running forgetSkippable.
     nsCycleCollector_dispatchDeferredDeletion();
 
-    sCCRunner =
-      CollectorRunner::Create(CCRunnerFired, NS_CC_SKIPPABLE_DELAY,
-                              kForgetSkippableSliceDuration, true);
+    sCCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+    sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+                                        NS_CC_SKIPPABLE_DELAY,
+                                        nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
+                                        "CCTimerFired");
   }
 }
 
 //static
 void
 nsJSContext::KillGCTimer()
 {
   if (sGCTimer) {
@@ -2226,54 +2056,54 @@ nsJSContext::KillFullGCTimer()
 {
   if (sFullGCTimer) {
     sFullGCTimer->Cancel();
     NS_RELEASE(sFullGCTimer);
   }
 }
 
 void
-nsJSContext::KillInterSliceGCRunner()
+nsJSContext::KillInterSliceGCTimer()
 {
-  if (sInterSliceGCRunner) {
-    sInterSliceGCRunner->Cancel();
-    sInterSliceGCRunner = nullptr;
+  if (sInterSliceGCTimer) {
+    sInterSliceGCTimer->Cancel();
+    NS_RELEASE(sInterSliceGCTimer);
   }
 }
 
 //static
 void
 nsJSContext::KillShrinkingGCTimer()
 {
   if (sShrinkingGCTimer) {
     sShrinkingGCTimer->Cancel();
     NS_RELEASE(sShrinkingGCTimer);
   }
 }
 
 //static
 void
-nsJSContext::KillCCRunner()
+nsJSContext::KillCCTimer()
 {
   sCCLockedOutTime = 0;
-  if (sCCRunner) {
-    sCCRunner->Cancel();
-    sCCRunner = nullptr;
+  if (sCCTimer) {
+    sCCTimer->Cancel();
+    NS_RELEASE(sCCTimer);
   }
 }
 
 //static
 void
-nsJSContext::KillICCRunner()
+nsJSContext::KillICCTimer()
 {
   sCCLockedOutTime = 0;
 
-  if (sICCRunner) {
-    sICCRunner->Cancel();
-    sICCRunner = nullptr;
+  if (sICCTimer) {
+    sICCTimer->Cancel();
+    NS_RELEASE(sICCTimer);
   }
 }
 
 class NotifyGCEndRunnable : public Runnable
 {
   nsString mMessage;
 
 public:
@@ -2335,18 +2165,18 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
           RefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
           NS_DispatchToMainThread(notify);
         }
       }
 
       sCCLockedOut = false;
       sIsCompactingOnUserInactive = false;
 
-      // May need to kill the inter-slice GC runner
-      nsJSContext::KillInterSliceGCRunner();
+      // May need to kill the inter-slice GC timer
+      nsJSContext::KillInterSliceGCTimer();
 
       sCCollectedWaitingForGC = 0;
       sCCollectedZonesWaitingForGC = 0;
       sLikelyShortLivingObjectsNeedingGC = 0;
       sCleanupsSinceLastGC = 0;
       sNeedsFullCC = true;
       sHasRunGC = true;
       nsJSContext::MaybePokeCC();
@@ -2377,21 +2207,25 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
     }
 
     case JS::GC_SLICE_BEGIN:
       break;
 
     case JS::GC_SLICE_END:
 
       // Schedule another GC slice if the GC has more work to do.
-      nsJSContext::KillInterSliceGCRunner();
+      nsJSContext::KillInterSliceGCTimer();
       if (!sShuttingDown && !aDesc.isComplete_) {
-        sInterSliceGCRunner =
-          CollectorRunner::Create(InterSliceGCRunnerFired, NS_INTERSLICE_GC_DELAY,
-                                  sActiveIntersliceGCBudget, false);
+        CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
+        sInterSliceGCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+        sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired,
+                                                      nullptr,
+                                                      NS_INTERSLICE_GC_DELAY,
+                                                      nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
+                                                      "InterSliceGCTimerFired");
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
         nsString prefix, gcstats;
@@ -2435,17 +2269,17 @@ nsJSContext::LikelyShortLivingObjectCrea
 {
   ++sLikelyShortLivingObjectsNeedingGC;
 }
 
 void
 mozilla::dom::StartupJSEnvironment()
 {
   // initialize all our statics, so that we can restart XPCOM
-  sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr;
+  sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr;
   sCCLockedOut = false;
   sCCLockedOutTime = 0;
   sLastCCEndTime = TimeStamp();
   sHasRunGC = false;
   sPendingLoadCount = 0;
   sLoadingInProgress = false;
   sCCollectedWaitingForGC = 0;
   sCCollectedZonesWaitingForGC = 0;
@@ -2770,16 +2604,62 @@ nsJSContext::EnsureStatics()
   obs->AddObserver(observer, "user-interaction-inactive", false);
   obs->AddObserver(observer, "user-interaction-active", false);
   obs->AddObserver(observer, "quit-application", false);
   obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 
   sIsInitialized = true;
 }
 
+void
+nsJSContext::NotifyDidPaint()
+{
+  sDidPaintAfterPreviousICCSlice = true;
+  if (sICCTimer) {
+    static uint32_t sCount = 0;
+    // 16 here is the common value for refresh driver tick frequency.
+    static const uint32_t kTicksPerSliceDelay = kICCIntersliceDelay / 16;
+    if (++sCount % kTicksPerSliceDelay != 0) {
+      // Don't trigger CC slice all the time after paint, but often still.
+      // The key point is to trigger it right after paint, especially when
+      // we're running RefreshDriver constantly.
+      return;
+    }
+
+    sICCTimer->Cancel();
+    ICCTimerFired(nullptr, nullptr);
+    if (sICCTimer) {
+      sICCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+      sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+                                           kICCIntersliceDelay,
+                                           nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
+                                           "ICCTimerFired");
+    }
+  } else if (sCCTimer) {
+    static uint32_t sCount = 0;
+    static const uint32_t kTicksPerForgetSkippableDelay =
+      NS_CC_SKIPPABLE_DELAY / 16;
+    if (++sCount % kTicksPerForgetSkippableDelay != 0) {
+      // The comment above about triggering CC slice applies to forget skippable
+      // too.
+      return;
+    }
+
+    sCCTimer->Cancel();
+    CCTimerFired(nullptr, nullptr);
+    if (sCCTimer) {
+      sCCTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+      sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+                                          NS_CC_SKIPPABLE_DELAY,
+                                          nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY,
+                                          "CCTimerFired");
+    }
+  }
+}
+
 nsScriptNameSpaceManager*
 mozilla::dom::GetNameSpaceManager()
 {
   if (sDidShutdown)
     return nullptr;
 
   if (!gNameSpaceManager) {
     gNameSpaceManager = new nsScriptNameSpaceManager;
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -10,17 +10,16 @@
 #include "nsIScriptGlobalObject.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "prtime.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIXPConnect.h"
 #include "nsIArray.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 class nsICycleCollectorListener;
 class nsScriptNameSpaceManager;
 
 namespace JS {
 class AutoValueVector;
@@ -87,17 +86,17 @@ public:
                                 int64_t aSliceMillis = 0);
 
   // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
   // called even if the previous collection was GC.
   static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr,
                               int32_t aExtraForgetSkippableCalls = 0);
 
   // Run a cycle collector slice, using a heuristic to decide how long to run it.
-  static void RunCycleCollectorSlice(mozilla::TimeStamp aDeadline);
+  static void RunCycleCollectorSlice();
 
   // Run a cycle collector slice, using the given work budget.
   static void RunCycleCollectorWorkSlice(int64_t aWorkBudget);
 
   static void BeginCycleCollectionCallback();
   static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults);
 
   // Return the longest CC slice time since ClearMaxCCSliceTime() was last called.
@@ -109,34 +108,35 @@ public:
   // The GC should probably run soon, in the zone of object aObj (if given).
   static void PokeGC(JS::gcreason::Reason aReason, JSObject* aObj, int aDelay = 0);
   static void KillGCTimer();
 
   static void PokeShrinkingGC();
   static void KillShrinkingGCTimer();
 
   static void MaybePokeCC();
-  static void KillCCRunner();
-  static void KillICCRunner();
+  static void KillCCTimer();
+  static void KillICCTimer();
   static void KillFullGCTimer();
-  static void KillInterSliceGCRunner();
+  static void KillInterSliceGCTimer();
 
   // Calling LikelyShortLivingObjectCreated() makes a GC more likely.
   static void LikelyShortLivingObjectCreated();
 
   static uint32_t CleanupsSinceLastGC();
 
   nsIScriptGlobalObject* GetCachedGlobalObject()
   {
     // Verify that we have a global so that this
     // does always return a null when GetGlobalObject() is null.
     JSObject* global = GetWindowProxy();
     return global ? mGlobalObjectRef.get() : nullptr;
   }
 
+  static void NotifyDidPaint();
 protected:
   virtual ~nsJSContext();
 
   // Helper to convert xpcom datatypes to jsvals.
   nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
                                    JS::Handle<JSObject*> aScope,
                                    JS::AutoValueVector &aArgsOut);
 
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1692,56 +1692,16 @@ nsRefreshDriver::RunFrameRequestCallback
       }
       for (auto& callback : docCallbacks.mCallbacks) {
         callback->Call(timeStamp);
       }
     }
   }
 }
 
-struct RunnableWithDelay
-{
-  nsCOMPtr<nsIRunnable> mRunnable;
-  uint32_t mDelay;
-};
-
-static AutoTArray<RunnableWithDelay, 8>* sPendingIdleRunnables = nullptr;
-
-void
-nsRefreshDriver::DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
-                                               uint32_t aDelay)
-{
-  if (!sPendingIdleRunnables) {
-    sPendingIdleRunnables = new AutoTArray<RunnableWithDelay, 8>();
-  }
-
-  RunnableWithDelay rwd = {aRunnable, aDelay};
-  sPendingIdleRunnables->AppendElement(rwd);
-}
-
-void
-nsRefreshDriver::CancelIdleRunnable(nsIRunnable* aRunnable)
-{
-  if (!sPendingIdleRunnables) {
-    return;
-  }
-
-  for (uint32_t i = 0; i < sPendingIdleRunnables->Length(); ++i) {
-    if ((*sPendingIdleRunnables)[i].mRunnable == aRunnable) {
-      sPendingIdleRunnables->RemoveElementAt(i);
-      break;
-    }
-  }
-
-  if (sPendingIdleRunnables->IsEmpty()) {
-    delete sPendingIdleRunnables;
-    sPendingIdleRunnables = nullptr;
-  }
-}
-
 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!");
@@ -1994,17 +1954,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
       }
     }
 
     for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
       imagesToRefresh[i]->RequestRefresh(aNowTime);
     }
   }
 
-  bool dispatchRunnablesAfterTick = false;
+  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
@@ -2033,17 +1993,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
 #endif
 
     for (nsDocShell* docShell : profilingDocShells) {
       MOZ_ASSERT(timelines);
       MOZ_ASSERT(timelines->HasConsumer(docShell));
       timelines->AddMarkerForDocShell(docShell, "Paint",  MarkerTracingType::END);
     }
 
-    dispatchRunnablesAfterTick = true;
+    notifyGC = true;
   }
 
 #ifndef ANDROID  /* bug 1142079 */
   mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart);
 #endif
 
   nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers);
   while (iter.HasMore()) {
@@ -2052,24 +2012,20 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
   }
 
   NS_ASSERTION(mInRefresh, "Still in refresh");
 
   if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
     ScheduleViewManagerFlush();
   }
 
-  if (dispatchRunnablesAfterTick && sPendingIdleRunnables) {
-    AutoTArray<RunnableWithDelay, 8>* runnables = sPendingIdleRunnables;
-    sPendingIdleRunnables = nullptr;
-    for (uint32_t i = 0; i < runnables->Length(); ++i) {
-      NS_IdleDispatchToCurrentThread((*runnables)[i].mRunnable.forget(),
-                                     (*runnables)[i].mDelay);
-    }
-    delete runnables;
+  if (notifyGC && nsContentUtils::XPConnect()) {
+    GeckoProfilerTracingRAII tracer("Paint", "NotifyDidPaint");
+    nsContentUtils::XPConnect()->NotifyDidPaint();
+    nsJSContext::NotifyDidPaint();
   }
 }
 
 void
 nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
                                        mozilla::TimeStamp aDesired)
 {
   for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -28,17 +28,16 @@
 #include "mozilla/layers/TransactionIdAllocator.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
 class nsIDOMEvent;
 class nsINode;
-class nsIRunnable;
 
 namespace mozilla {
 class RefreshDriverTimer;
 namespace layout {
 class VsyncChild;
 } // namespace layout
 } // namespace mozilla
 
@@ -329,20 +328,16 @@ public:
    * expected deadline. If the next expected deadline is later than
    * the default value, the default value is returned.
    *
    * If we're animating and we have skipped paints a time in the past
    * is returned.
    */
   static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
 
-  static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
-                                            uint32_t aDelay);
-  static void CancelIdleRunnable(nsIRunnable* aRunnable);
-
   bool SkippedPaints() const
   {
     return mSkippedPaints;
   }
 
 private:
   typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
   typedef nsTHashtable<nsISupportsHashKey> RequestTable;
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -139,17 +139,17 @@ function SetFailureTimeout(cb, timeout)
 }
 
 function StartTestURI(type, uri, timeout)
 {
     // The GC is only able to clean up compartments after the CC runs. Since
     // the JS ref tests disable the normal browser chrome and do not otherwise
     // create substatial DOM garbage, the CC tends not to run enough normally.
     ++gTestCount;
-    if (gTestCount % 500 == 0) {
+    if (gTestCount % 1000 == 0) {
         CU.forceGC();
         CU.forceCC();
     }
 
     // Reset gExplicitPendingPaintCount in case there was a timeout or
     // the count is out of sync for some other reason
     if (gExplicitPendingPaintCount != 0) {
         LogWarning("Resetting gExplicitPendingPaintCount to zero (currently " +
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1388,17 +1388,17 @@ pref("javascript.options.discardSystemSo
 // This preference limits the memory usage of javascript.
 // If you want to change these values for your device,
 // please find Bug 417052 comment 17 and Bug 456721
 // Comment 32 and Bug 613551.
 pref("javascript.options.mem.high_water_mark", 128);
 pref("javascript.options.mem.max", -1);
 pref("javascript.options.mem.gc_per_zone", true);
 pref("javascript.options.mem.gc_incremental", true);
-pref("javascript.options.mem.gc_incremental_slice_ms", 5);
+pref("javascript.options.mem.gc_incremental_slice_ms", 10);
 pref("javascript.options.mem.gc_compacting", true);
 pref("javascript.options.mem.log", false);
 pref("javascript.options.mem.notify", false);
 pref("javascript.options.gc_on_memory_pressure", true);
 pref("javascript.options.compact_on_user_inactive", true);
 #ifdef NIGHTLY_BUILD
 pref("javascript.options.compact_on_user_inactive_delay", 15000); // ms
 #else