diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index c7e17630ab..4e3eba8277 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -50,6 +50,8 @@ #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" @@ -57,6 +59,7 @@ #include "nsAXPCNativeCallContext.h" #include "mozilla/CycleCollectedJSContext.h" +#include "nsRefreshDriver.h" #include "nsJSPrincipals.h" #ifdef XP_MACOSX @@ -104,20 +107,24 @@ const size_t gStackSize = 8192; // 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, 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 +// Time budget for an incremental CC slice when using timer to run it. 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 @@ -136,14 +143,16 @@ static const uint32_t kMaxICCDuration = 2000; // ms // 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 nsITimer *sCCTimer; -static nsITimer *sICCTimer; +static StaticRefPtr sCCRunner; +static StaticRefPtr sICCRunner; static nsITimer *sFullGCTimer; -static nsITimer *sInterSliceGCTimer; +static StaticRefPtr sInterSliceGCRunner; static TimeStamp sLastCCEndTime; @@ -168,7 +177,7 @@ static uint32_t sCCollectedZonesWaitingForGC; static uint32_t sLikelyShortLivingObjectsNeedingGC; static bool sPostGCEventsToConsole; static bool sPostGCEventsToObserver; -static int32_t sCCTimerFireCount = 0; +static int32_t sCCRunnerFireCount = 0; static uint32_t sMinForgetSkippableTime = UINT32_MAX; static uint32_t sMaxForgetSkippableTime = 0; static uint32_t sTotalForgetSkippableTime = 0; @@ -180,7 +189,6 @@ static bool sNeedsFullCC = false; static bool sNeedsFullGC = false; static bool sNeedsGCAfterCC = false; static bool sIncrementalCC = false; -static bool sDidPaintAfterPreviousICCSlice = false; static nsScriptNameSpaceManager *gNameSpaceManager; @@ -215,6 +223,183 @@ static bool sIsCompactingOnUserInactive = false; 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 + Create(CollectorRunnerCallback aCallback, uint32_t aDelay, + int64_t aBudget, bool aRepeating, void* aData = nullptr) + { + if (sShuttingDown) { + return nullptr; + } + + RefPtr 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 runnable = static_cast(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(); + mTimer = nullptr; + mScheduleTimer = nullptr; + mCallback = nullptr; + return NS_OK; + } + + static void + ScheduleTimedOut(nsITimer* aTimer, void* aClosure) + { + RefPtr runnable = static_cast(aClosure); + runnable->Schedule(true); + } + + void Schedule(bool aAllowIdleDispatch) + { + if (!mCallback) { + return; + } + + if (sShuttingDown) { + Cancel(); + 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 runnable = this; + NS_IdleDispatchToCurrentThread(runnable.forget(), mDelay); + SetTimer(mDelay, mTarget); + } else { + if (!mScheduleTimer) { + mScheduleTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (!mScheduleTimer) { + return; + } + } else { + mScheduleTimer->Cancel(); + } + + // We weren't allowed to do idle dispatch immediately, do it after a + // short timeout. + mScheduleTimer->InitWithFuncCallback(ScheduleTimedOut, this, 16, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + } + } + } + +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(); + } + if (mScheduleTimer) { + mScheduleTimer->Cancel(); + } + mTimerActive = false; + } + + nsCOMPtr mTimer; + nsCOMPtr mScheduleTimer; + nsCOMPtr mTarget; + CollectorRunnerCallback mCallback; + uint32_t mDelay; + TimeStamp mDeadline; + TimeDuration mBudget; + bool mRepeating; + bool mTimerActive; + void* mData; +}; + static const char* ProcessNameForCollectorLog() { @@ -302,10 +487,10 @@ KillTimers() { nsJSContext::KillGCTimer(); nsJSContext::KillShrinkingGCTimer(); - nsJSContext::KillCCTimer(); - nsJSContext::KillICCTimer(); + nsJSContext::KillCCRunner(); + nsJSContext::KillICCRunner(); nsJSContext::KillFullGCTimer(); - nsJSContext::KillInterSliceGCTimer(); + nsJSContext::KillInterSliceGCRunner(); } // If we collected a substantial amount of cycles, poke the GC since more objects @@ -1346,7 +1531,7 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener, //static void -nsJSContext::RunCycleCollectorSlice() +nsJSContext::RunCycleCollectorSlice(TimeStamp aDeadline) { if (!NS_IsMainThread()) { return; @@ -1362,33 +1547,39 @@ nsJSContext::RunCycleCollectorSlice() 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(kICCSliceBudget)); + budget = js::SliceBudget(js::TimeBudget(baseBudget)); } 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 = kICCSliceBudget * sliceDelayMultiplier; + float sliceDelayMultiplier = + TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay; + float delaySliceBudget = + std::min(baseBudget * sliceDelayMultiplier, maxSlice); - // Increase slice budgets up to |maxLaterSlice| as we approach + // Increase slice budgets up to |maxSlice| as we approach // half way through the ICC, to avoid large sync CCs. float percentToHalfDone = std::min(2.0f * runningTime / kMaxICCDuration, 1.0f); - const float maxLaterSlice = 40.0f; - float laterSliceBudget = maxLaterSlice * percentToHalfDone; + float laterSliceBudget = maxSlice * percentToHalfDone; budget = js::SliceBudget(js::TimeBudget(std::max({delaySliceBudget, - laterSliceBudget, (float)kICCSliceBudget}))); + laterSliceBudget, (float)baseBudget}))); } } } - nsCycleCollector_collectSlice(budget, sDidPaintAfterPreviousICCSlice); - sDidPaintAfterPreviousICCSlice = false; + nsCycleCollector_collectSlice(budget); gCCStats.FinishCycleCollectionSlice(); } @@ -1424,11 +1615,11 @@ nsJSContext::GetMaxCCSliceTimeSinceClear() return gCCStats.mMaxSliceTimeSinceClear; } -static void -ICCTimerFired(nsITimer* aTimer, void* aClosure) +static bool +ICCRunnerFired(TimeStamp aDeadline, void* aData) { if (sDidShutdown) { - return; + return false; } // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us @@ -1438,14 +1629,15 @@ ICCTimerFired(nsITimer* aTimer, void* aClosure) PRTime now = PR_Now(); if (sCCLockedOutTime == 0) { sCCLockedOutTime = now; - return; + return false; } if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) { - return; + return false; } } - nsJSContext::RunCycleCollectorSlice(); + nsJSContext::RunCycleCollectorSlice(aDeadline); + return true; } //static @@ -1457,21 +1649,16 @@ nsJSContext::BeginCycleCollectionCallback() gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime; gCCStats.mSuspected = nsCycleCollector_suspectedCount(); - KillCCTimer(); + KillCCRunner(); gCCStats.RunForgetSkippable(); - MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed."); + MOZ_ASSERT(!sICCRunner, "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. - CallCreateInstance("@mozilla.org/timer;1", &sICCTimer); - if (sICCTimer) { - sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr, - kICCIntersliceDelay, - nsITimer::TYPE_REPEATING_SLACK, - "ICCTimerFired"); - } + sICCRunner = CollectorRunner::Create(ICCRunnerFired, kICCIntersliceDelay, + kIdleICCSliceBudget, true); } static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0"); @@ -1482,7 +1669,7 @@ nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults) { MOZ_ASSERT(NS_IsMainThread()); - nsJSContext::KillICCTimer(); + nsJSContext::KillICCRunner(); // Update timing information for the current slice before we log it, if // we previously called PrepareForCycleCollectionSlice(). During shutdown @@ -1616,14 +1803,28 @@ nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults) } // static -void -InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure) +bool +InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData) { - nsJSContext::KillInterSliceGCTimer(); - nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC, + nsJSContext::KillInterSliceGCRunner(); + MOZ_ASSERT(sActiveIntersliceGCBudget > 0); + // We use longer budgets when timer runs since that means + // there hasn't been idle time recently and we may have significant amount + // garbage to collect. + int64_t budget = sActiveIntersliceGCBudget * 2; + if (!aDeadline.IsNull()) { + budget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds()); + } + + uintptr_t reason = reinterpret_cast(aData); + nsJSContext::GarbageCollectNow(aData ? + static_cast(reason) : + JS::gcreason::INTER_SLICE_GC, nsJSContext::IncrementalGC, nsJSContext::NonShrinkingGC, NS_INTERSLICE_GC_BUDGET); + + return true; } // static @@ -1631,9 +1832,12 @@ void GCTimerFired(nsITimer *aTimer, void *aClosure) { nsJSContext::KillGCTimer(); - uintptr_t reason = reinterpret_cast(aClosure); - nsJSContext::GarbageCollectNow(static_cast(reason), - nsJSContext::IncrementalGC); + // Now start the actual GC after initial timer has fired. + sInterSliceGCRunner = CollectorRunner::Create(InterSliceGCRunnerFired, + NS_INTERSLICE_GC_DELAY, + sActiveIntersliceGCBudget, + false, + aClosure); } // static @@ -1656,11 +1860,11 @@ ShouldTriggerCC(uint32_t aSuspected) TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED); } -static void -CCTimerFired(nsITimer *aTimer, void *aClosure) +static bool +CCRunnerFired(TimeStamp aDeadline, void* aData) { if (sDidShutdown) { - return; + return false; } static uint32_t ccDelay = NS_CC_DELAY; @@ -1669,48 +1873,53 @@ CCTimerFired(nsITimer *aTimer, void *aClosure) PRTime now = PR_Now(); if (sCCLockedOutTime == 0) { - // Reset sCCTimerFireCount so that we run forgetSkippable + // Reset sCCRunnerFireCount 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. - sCCTimerFireCount = 0; + sCCRunnerFireCount = 0; sCCLockedOutTime = now; - return; + return false; } if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) { - return; + return false; } } - ++sCCTimerFireCount; + ++sCCRunnerFireCount; + + bool didDoWork = false; // 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 = sCCTimerFireCount > numEarlyTimerFires; + bool isLateTimerFire = sCCRunnerFireCount > numEarlyTimerFires; uint32_t suspected = nsCycleCollector_suspectedCount(); if (isLateTimerFire && ShouldTriggerCC(suspected)) { - if (sCCTimerFireCount == numEarlyTimerFires + 1) { + if (sCCRunnerFireCount == 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; + return didDoWork; } } 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(); + nsJSContext::RunCycleCollectorSlice(aDeadline); + didDoWork = true; } } 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) { @@ -1719,8 +1928,9 @@ CCTimerFired(nsITimer *aTimer, void *aClosure) // 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::KillCCTimer(); + nsJSContext::KillCCRunner(); } + return didDoWork; } // static @@ -1769,13 +1979,13 @@ ReadyToTriggerExpensiveCollectorTimer() } -// 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 +// 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 // always run their corresponding timer. -// This does not check sFullGCTimer, as that's an even more expensive collection we run +// This does not check sFullGCTimer, as that's a more expensive collection we run // on a long timer. // static @@ -1793,8 +2003,8 @@ nsJSContext::RunNextCollectorTimer() return; } - if (sInterSliceGCTimer) { - InterSliceGCTimerFired(nullptr, nullptr); + if (sInterSliceGCRunner) { + InterSliceGCRunnerFired(TimeStamp(), nullptr); return; } @@ -1802,15 +2012,15 @@ nsJSContext::RunNextCollectorTimer() // anything if a GC is in progress. MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out."); - if (sCCTimer) { + if (sCCRunner) { if (ReadyToTriggerExpensiveCollectorTimer()) { - CCTimerFired(nullptr, nullptr); + CCRunnerFired(TimeStamp(), nullptr); } return; } - if (sICCTimer) { - ICCTimerFired(nullptr, nullptr); + if (sICCRunner) { + ICCRunnerFired(TimeStamp(), nullptr); return; } } @@ -1821,12 +2031,19 @@ nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay) { sNeedsFullGC = sNeedsFullGC || aReason != JS::gcreason::CC_WAITING; - if (sGCTimer || sInterSliceGCTimer || sShuttingDown) { + if (aObj) { + JS::Zone* zone = JS::GetObjectZone(aObj); + CycleCollectedJSContext::Get()->AddZoneWaitingForGC(zone); + } else if (aReason != JS::gcreason::CC_WAITING) { + sNeedsFullGC = true; + } + + if (sGCTimer || sInterSliceGCRunner) { // There's already a timer for GC'ing, just return return; } - if (sCCTimer) { + if (sCCRunner) { // Make sure CC is called... sNeedsFullCC = true; // and GC after it. @@ -1834,7 +2051,7 @@ nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay) return; } - if (sICCTimer) { + if (sICCRunner) { // Make sure GC is called after the current CC completes. // No need to set sNeedsFullCC because we are currently running a CC. sNeedsGCAfterCC = true; @@ -1887,23 +2104,19 @@ nsJSContext::PokeShrinkingGC() void nsJSContext::MaybePokeCC() { - if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) { + if (sCCRunner || sICCRunner || sShuttingDown || !sHasRunGC) { return; } if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { - sCCTimerFireCount = 0; - CallCreateInstance("@mozilla.org/timer;1", &sCCTimer); - if (!sCCTimer) { - return; - } + sCCRunnerFireCount = 0; + // We can kill some objects before running forgetSkippable. nsCycleCollector_dispatchDeferredDeletion(); - sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr, - NS_CC_SKIPPABLE_DELAY, - nsITimer::TYPE_REPEATING_SLACK, - "CCTimerFired"); + sCCRunner = + CollectorRunner::Create(CCRunnerFired, NS_CC_SKIPPABLE_DELAY, + kForgetSkippableSliceDuration, true); } } @@ -1927,11 +2140,11 @@ nsJSContext::KillFullGCTimer() } void -nsJSContext::KillInterSliceGCTimer() +nsJSContext::KillInterSliceGCRunner() { - if (sInterSliceGCTimer) { - sInterSliceGCTimer->Cancel(); - NS_RELEASE(sInterSliceGCTimer); + if (sInterSliceGCRunner) { + sInterSliceGCRunner->Cancel(); + sInterSliceGCRunner = nullptr; } } @@ -1947,12 +2160,12 @@ nsJSContext::KillShrinkingGCTimer() //static void -nsJSContext::KillCCTimer() +nsJSContext::KillICCRunner() { sCCLockedOutTime = 0; - if (sCCTimer) { - sCCTimer->Cancel(); - NS_RELEASE(sCCTimer); + if (sICCRunner) { + sICCRunner->Cancel(); + sICCRunner = nullptr; } } @@ -2036,8 +2249,8 @@ DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescrip sCCLockedOut = false; sIsCompactingOnUserInactive = false; - // May need to kill the inter-slice GC timer - nsJSContext::KillInterSliceGCTimer(); + // May need to kill the inter-slice GC runner + nsJSContext::KillInterSliceGCRunner(); sCCollectedWaitingForGC = 0; sCCollectedZonesWaitingForGC = 0; @@ -2073,14 +2286,11 @@ DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescrip case JS::GC_SLICE_END: // The GC has more work to do, so schedule another GC slice. - nsJSContext::KillInterSliceGCTimer(); + nsJSContext::KillInterSliceGCRunner(); if (!sShuttingDown) { - CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer); - sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired, - nullptr, - NS_INTERSLICE_GC_DELAY, - nsITimer::TYPE_ONE_SHOT, - "InterSliceGCTimerFired"); + sInterSliceGCRunner = + CollectorRunner::Create(InterSliceGCRunnerFired, NS_INTERSLICE_GC_DELAY, + sActiveIntersliceGCBudget, false); } if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { @@ -2134,7 +2344,7 @@ void mozilla::dom::StartupJSEnvironment() { // initialize all our statics, so that we can restart XPCOM - sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr; + sGCTimer = sShrinkingGCTimer = sFullGCTimer = nullptr; sCCLockedOut = false; sCCLockedOutTime = 0; sLastCCEndTime = TimeStamp(); @@ -2471,50 +2681,6 @@ nsJSContext::EnsureStatics() 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->InitWithNamedFuncCallback(ICCTimerFired, nullptr, - kICCIntersliceDelay, - nsITimer::TYPE_REPEATING_SLACK, - "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->InitWithNamedFuncCallback(CCTimerFired, nullptr, - NS_CC_SKIPPABLE_DELAY, - nsITimer::TYPE_REPEATING_SLACK, - "CCTimerFired"); - } - } -} - nsScriptNameSpaceManager* mozilla::dom::GetNameSpaceManager() { diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h index 0124f726db..25cd03ec90 100644 --- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -14,6 +14,7 @@ #include "nsIXPConnect.h" #include "nsIArray.h" #include "mozilla/Attributes.h" +#include "mozilla/TimeStamp.h" #include "nsThreadUtils.h" #include "xpcpublic.h" @@ -90,7 +91,7 @@ public: int32_t aExtraForgetSkippableCalls = 0); // Run a cycle collector slice, using a heuristic to decide how long to run it. - static void RunCycleCollectorSlice(); + static void RunCycleCollectorSlice(mozilla::TimeStamp aDeadline); // Run a cycle collector slice, using the given work budget. static void RunCycleCollectorWorkSlice(int64_t aWorkBudget); @@ -111,10 +112,10 @@ public: static void KillShrinkingGCTimer(); static void MaybePokeCC(); - static void KillCCTimer(); - static void KillICCTimer(); + static void KillCCRunner(); + static void KillICCRunner(); static void KillFullGCTimer(); - static void KillInterSliceGCTimer(); + static void KillInterSliceGCRunner(); // Calling LikelyShortLivingObjectCreated() makes a GC more likely. static void LikelyShortLivingObjectCreated(); @@ -129,7 +130,6 @@ public: return global ? mGlobalObjectRef.get() : nullptr; } - static void NotifyDidPaint(); protected: virtual ~nsJSContext(); diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 58157d8883..27d8f0189e 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -1663,6 +1663,46 @@ nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime) } } +struct RunnableWithDelay +{ + nsCOMPtr mRunnable; + uint32_t mDelay; +}; + +static AutoTArray* sPendingIdleRunnables = nullptr; + +void +nsRefreshDriver::DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable, + uint32_t aDelay) +{ + if (!sPendingIdleRunnables) { + sPendingIdleRunnables = new AutoTArray(); + } + + 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) { @@ -1944,7 +1984,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) } mPresShellsToInvalidateIfHidden.Clear(); - bool notifyGC = false; + bool dispatchRunnablesAfterTick = false; if (mViewManagerFlushIsPending) { RefPtr timelines = TimelineConsumers::Get(); @@ -1982,7 +2022,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::END); } - notifyGC = true; + dispatchRunnablesAfterTick = true; } nsTObserverArray::ForwardIterator iter(mPostRefreshObservers); @@ -1999,9 +2039,14 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) ScheduleViewManagerFlush(); } - if (notifyGC && nsContentUtils::XPConnect()) { - nsContentUtils::XPConnect()->NotifyDidPaint(); - nsJSContext::NotifyDidPaint(); + if (dispatchRunnablesAfterTick && sPendingIdleRunnables) { + AutoTArray* runnables = sPendingIdleRunnables; + sPendingIdleRunnables = nullptr; + for (uint32_t i = 0; i < runnables->Length(); ++i) { + NS_IdleDispatchToCurrentThread((*runnables)[i].mRunnable.forget(), + (*runnables)[i].mDelay); + } + delete runnables; } } diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index b2a2ce5404..ce929043e1 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -32,6 +32,7 @@ class nsIDocument; class imgIRequest; class nsIDOMEvent; class nsINode; +class nsIRunnable; namespace mozilla { class RefreshDriverTimer; @@ -364,6 +365,10 @@ public: */ static mozilla::Maybe GetIdleDeadlineHint(); + static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable, + uint32_t aDelay); + static void CancelIdleRunnable(nsIRunnable* aRunnable); + bool SkippedPaints() const { return mSkippedPaints; diff --git a/layout/tools/reftest/reftest-content.js b/layout/tools/reftest/reftest-content.js index 04e4714ff1..cb5e43dff9 100644 --- a/layout/tools/reftest/reftest-content.js +++ b/layout/tools/reftest/reftest-content.js @@ -39,7 +39,6 @@ var gTimeoutHook = null; var gFailureTimeout = null; var gFailureReason; var gAssertionCount = 0; -var gTestCount = 0; var gDebug; var gVerbose = false; @@ -143,11 +142,7 @@ 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 % 1000 == 0) { - CU.forceGC(); - CU.forceCC(); - } + windowUtils().runNextCollectorTimer(); // Reset gExplicitPendingPaintCount in case there was a timeout or // the count is out of sync for some other reason diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index e13c9e94c9..74a843de40 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1298,8 +1298,7 @@ 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", 10); -pref("javascript.options.mem.gc_generational", true); +pref("javascript.options.mem.gc_incremental_slice_ms", 15); pref("javascript.options.mem.gc_compacting", true); pref("javascript.options.mem.log", false); pref("javascript.options.mem.notify", false);