diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 35bc0df9233..e9e7b97ab3f 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -107,6 +107,10 @@ const size_t gStackSize = 8192; #define NS_FULL_GC_DELAY 60000 // ms +// The amount of time to wait from the user being idle to starting a shrinking +// GC. +#define NS_SHRINKING_GC_DELAY 15000 // ms + // Maximum amount of time that should elapse between incremental GC slices #define NS_INTERSLICE_GC_DELAY 100 // ms @@ -148,6 +152,7 @@ static const uint32_t kMaxICCDuration = 2000; // ms static nsITimer *sGCTimer; static nsITimer *sShrinkGCBuffersTimer; +static nsITimer *sShrinkingGCTimer; static nsITimer *sCCTimer; static nsITimer *sICCTimer; static nsITimer *sFullGCTimer; @@ -212,6 +217,7 @@ static nsIScriptSecurityManager *sSecurityManager; // the appropriate pref is set. static bool sGCOnMemoryPressure; +static bool sCompactOnUserInactive; // In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more // aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps @@ -234,6 +240,7 @@ static void KillTimers() { nsJSContext::KillGCTimer(); + nsJSContext::KillShrinkingGCTimer(); nsJSContext::KillShrinkGCBuffersTimer(); nsJSContext::KillCCTimer(); nsJSContext::KillICCTimer(); @@ -266,22 +273,30 @@ NS_IMETHODIMP nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { - if (sGCOnMemoryPressure && !nsCRT::strcmp(aTopic, "memory-pressure")) { - if(StringBeginsWith(nsDependentString(aData), - NS_LITERAL_STRING("low-memory-ongoing"))) { - // Don't GC/CC if we are in an ongoing low-memory state since its very - // slow and it likely won't help us anyway. - return NS_OK; - } - nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE, - nsJSContext::NonIncrementalGC, - nsJSContext::ShrinkingGC); - nsJSContext::CycleCollectNow(); - if (NeedsGCAfterCC()) { + if (!nsCRT::strcmp(aTopic, "memory-pressure")) { + if (sGCOnMemoryPressure) { + if(StringBeginsWith(nsDependentString(aData), + NS_LITERAL_STRING("low-memory-ongoing"))) { + // Don't GC/CC if we are in an ongoing low-memory state since its very + // slow and it likely won't help us anyway. + return NS_OK; + } nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE, nsJSContext::NonIncrementalGC, nsJSContext::ShrinkingGC); + nsJSContext::CycleCollectNow(); + if (NeedsGCAfterCC()) { + nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE, + nsJSContext::NonIncrementalGC, + nsJSContext::ShrinkingGC); + } } + } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) { + if (sCompactOnUserInactive) { + nsJSContext::PokeShrinkingGC(); + } + } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) { + nsJSContext::KillShrinkingGCTimer(); } else if (!nsCRT::strcmp(aTopic, "quit-application") || !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { sShuttingDown = true; @@ -1284,12 +1299,11 @@ nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason, return; } + JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL; JS::PrepareForFullGC(sRuntime); if (aIncremental == IncrementalGC) { - MOZ_ASSERT(aShrinking == NonShrinkingGC); - JS::StartIncrementalGC(sRuntime, GC_NORMAL, aReason, aSliceMillis); + JS::StartIncrementalGC(sRuntime, gckind, aReason, aSliceMillis); } else { - JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL; JS::GCForReason(sRuntime, gckind, aReason); } } @@ -1798,6 +1812,16 @@ ShrinkGCBuffersTimerFired(nsITimer *aTimer, void *aClosure) nsJSContext::ShrinkGCBuffersNow(); } +// static +void +ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure) +{ + nsJSContext::KillShrinkingGCTimer(); + nsJSContext::GarbageCollectNow(JS::gcreason::USER_INACTIVE, + nsJSContext::IncrementalGC, + nsJSContext::ShrinkingGC); +} + static bool ShouldTriggerCC(uint32_t aSuspected) { @@ -2030,6 +2054,26 @@ nsJSContext::PokeShrinkGCBuffers() nsITimer::TYPE_ONE_SHOT); } +// static +void +nsJSContext::PokeShrinkingGC() +{ + if (sShrinkingGCTimer || sShuttingDown) { + return; + } + + CallCreateInstance("@mozilla.org/timer;1", &sShrinkingGCTimer); + + if (!sShrinkingGCTimer) { + // Failed to create timer (probably because we're in XPCOM shutdown) + return; + } + + sShrinkingGCTimer->InitWithFuncCallback(ShrinkingGCTimerFired, nullptr, + NS_SHRINKING_GC_DELAY, + nsITimer::TYPE_ONE_SHOT); +} + // static void nsJSContext::MaybePokeCC() @@ -2091,6 +2135,16 @@ nsJSContext::KillShrinkGCBuffersTimer() } } +//static +void +nsJSContext::KillShrinkingGCTimer() +{ + if (sShrinkingGCTimer) { + sShrinkingGCTimer->Cancel(); + NS_RELEASE(sShrinkingGCTimer); + } +} + //static void nsJSContext::KillCCTimer() @@ -2282,7 +2336,7 @@ void mozilla::dom::StartupJSEnvironment() { // initialize all our statics, so that we can restart XPCOM - sGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr; + sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr; sCCLockedOut = false; sCCLockedOutTime = 0; sLastCCEndTime = TimeStamp(); @@ -2687,8 +2741,14 @@ nsJSContext::EnsureStatics() "javascript.options.gc_on_memory_pressure", true); + Preferences::AddBoolVarCache(&sCompactOnUserInactive, + "javascript.options.compact_on_user_inactive", + true); + nsIObserver* observer = new nsJSEnvironmentObserver(); obs->AddObserver(observer, "memory-pressure", false); + 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); diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h index 17bf0335a6c..3ebf50f5727 100644 --- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -116,6 +116,9 @@ public: static void PokeShrinkGCBuffers(); static void KillShrinkGCBuffersTimer(); + static void PokeShrinkingGC(); + static void KillShrinkingGCTimer(); + static void MaybePokeCC(); static void KillCCTimer(); static void KillICCTimer(); diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index 1c49d348737..6d67dc18ad1 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -99,7 +99,8 @@ namespace JS { D(REFRESH_FRAME) \ D(FULL_GC_TIMER) \ D(SHUTDOWN_CC) \ - D(FINISH_LARGE_EVALUATE) + D(FINISH_LARGE_EVALUATE) \ + D(USER_INACTIVE) namespace gcreason { diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 537290f2175..096123329ae 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -364,7 +364,9 @@ js::AssertSameCompartment(JSObject *objA, JSObject *objB) JS_FRIEND_API(void) js::NotifyAnimationActivity(JSObject *obj) { - obj->compartment()->lastAnimationTime = PRMJ_Now(); + int64_t timeNow = PRMJ_Now(); + obj->compartment()->lastAnimationTime = timeNow; + obj->runtimeFromMainThread()->lastAnimationTime = timeNow; } JS_FRIEND_API(uint32_t) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index c44e761c405..5995ab666a6 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1888,7 +1888,10 @@ ArenaLists::allocateFromArenaInner(JS::Zone *zone, ArenaHeader *aheader, AllocKi bool GCRuntime::shouldCompact() { - return invocationKind == GC_SHRINK && isCompactingGCEnabled(); + // Compact on shrinking GC if enabled, but skip compacting in incremental + // GCs if we are currently animating. + return invocationKind == GC_SHRINK && isCompactingGCEnabled() && + (!isIncremental || rt->lastAnimationTime + PRMJ_USEC_PER_SEC < PRMJ_Now()); } void diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 97e162e201b..a3dce47fbab 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -212,7 +212,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime) #endif largeAllocationFailureCallback(nullptr), oomCallback(nullptr), - debuggerMallocSizeOf(ReturnZeroSize) + debuggerMallocSizeOf(ReturnZeroSize), + lastAnimationTime(0) { setGCStoreBufferPtr(&gc.storeBuffer); diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 57bcf8a29e9..ef1104787f1 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1417,6 +1417,9 @@ struct JSRuntime : public JS::shadow::Runtime, * function to assess the size of malloc'd blocks of memory. */ mozilla::MallocSizeOf debuggerMallocSizeOf; + + /* Last time at which an animation was played for this runtime. */ + int64_t lastAnimationTime; }; namespace js { diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index f50f34c4e60..938eba86b43 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1081,6 +1081,7 @@ 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); pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 1000); pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 100);