Bug 1377131 - Try to trigger collector slices at times which disturb page js less (at least with iframes loaded after the top level page has been loaded), r=mccr8,bz
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Wed, 06 Sep 2017 18:18:11 +0100
changeset 379367 10405ae76f204c476465dcfaca4cd6b4edfcc444
parent 379366 753fa54f005b1fecda05d4c7c989e7c899b3ef32
child 379368 3067ce95b439f75c85c57f51d54623edf5eafa24
push id32453
push userarchaeopteryx@coole-files.de
push dateThu, 07 Sep 2017 10:39:44 +0000
treeherdermozilla-central@37b95547f0d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8, bz
bugs1377131
milestone57.0a1
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
Bug 1377131 - Try to trigger collector slices at times which disturb page js less (at least with iframes loaded after the top level page has been loaded), r=mccr8,bz When triggering an iframe load or starting to parse a document for an iframe, the main thread may often have some time before the new page has been created. Try to trigger CC/GC slice at such point in order to avoid collector later when page is already executing its JS
docshell/base/nsDocShell.cpp
dom/base/nsDOMWindowUtils.cpp
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
js/public/GCAPI.h
parser/html/nsHtml5StreamParser.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -217,16 +217,17 @@
 #include "nsIURIFixup.h"
 #include "nsIURILoader.h"
 #include "nsIURL.h"
 #include "nsIWebBrowserFind.h"
 #include "nsIWidget.h"
 #include "mozilla/dom/PerformanceNavigation.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/Encoding.h"
+#include "nsJSEnvironment.h"
 #include "IUrlClassifierUITelemetry.h"
 
 #ifdef MOZ_TOOLKIT_SEARCH
 #include "nsIBrowserSearchService.h"
 #endif
 
 #include "mozIThirdPartyUtil.h"
 
@@ -11699,16 +11700,21 @@ nsDocShell::DoChannelLoad(nsIChannel* aC
     openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
   }
   if (!mAllowContentRetargeting) {
     openFlags |= nsIURILoader::DONT_RETARGET;
   }
   rv = aURILoader->OpenURI(aChannel, openFlags, this);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // We're about to load a new page and it may take time before necko
+  // gives back any data, so main thread might have a chance to process a
+  // collector slice
+  nsJSContext::MaybeRunNextCollectorSlice(this, JS::gcreason::DOCSHELL);
+
   return NS_OK;
 }
 
 nsresult
 nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
                            nsACString& aNewHash, uint32_t aLoadType)
 {
   if (!mCurrentURI) {
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1433,17 +1433,17 @@ nsDOMWindowUtils::CycleCollect(nsICycleC
 {
   nsJSContext::CycleCollectNow(aListener);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::RunNextCollectorTimer()
 {
-  nsJSContext::RunNextCollectorTimer();
+  nsJSContext::RunNextCollectorTimer(JS::gcreason::DOM_WINDOW_UTILS);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendSimpleGestureEvent(const nsAString& aType,
                                          float aX,
                                          float aY,
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -78,20 +78,24 @@
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
 #include "mozilla/ContentEvents.h"
-
+#include "mozilla/CycleCollectedJSContext.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "GeckoProfiler.h"
 #include "mozilla/IdleTaskRunner.h"
+#include "nsIDocShell.h"
+#include "nsIPresShell.h"
+#include "nsViewManager.h"
+#include "mozilla/EventStateManager.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 const size_t gStackSize = 8192;
 
 // Thank you Microsoft!
 #ifdef CompareString
@@ -211,22 +215,16 @@ static bool sGCOnMemoryPressure;
 // nsJSEnvironmentObserver observes the user-interaction-inactive notifications
 // and triggers a shrinking a garbage collection if the user is still inactive
 // after NS_SHRINKING_GC_DELAY ms later, if the appropriate pref is set.
 
 static bool sCompactOnUserInactive;
 static uint32_t sCompactOnUserInactiveDelay = NS_DEAULT_INACTIVE_GC_DELAY;
 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;
-
 static TimeDuration sGCUnnotifiedTotalTime;
 
 static const char*
 ProcessNameForCollectorLog()
 {
   return XRE_GetProcessType() == GeckoProcessType_Default ?
     "default" : "content";
 }
@@ -1975,72 +1973,108 @@ nsJSContext::LoadEnd()
   if (sPendingLoadCount > 0) {
     --sPendingLoadCount;
     return;
   }
 
   sLoadingInProgress = false;
 }
 
-// Only trigger expensive timers when they have been checked a number of times.
-static bool
-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
-// always run their corresponding timer.
-
 // This does not check sFullGCTimer, as that's a more expensive collection we run
 // on a long timer.
 
 // static
 void
-nsJSContext::RunNextCollectorTimer()
+nsJSContext::RunNextCollectorTimer(JS::gcreason::Reason aReason,
+                                   mozilla::TimeStamp aDeadline)
 {
   if (sShuttingDown) {
     return;
   }
 
   if (sGCTimer) {
-    if (ReadyToTriggerExpensiveCollectorTimer()) {
-      GCTimerFired(nullptr, reinterpret_cast<void *>(JS::gcreason::DOM_WINDOW_UTILS));
-    }
-    return;
-  }
-
-  if (sInterSliceGCRunner) {
-    InterSliceGCRunnerFired(TimeStamp(), nullptr);
+    GCTimerFired(nullptr, reinterpret_cast<void*>(aReason));
     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.");
+  nsCOMPtr<nsIRunnable> runnable;
+  if (sInterSliceGCRunner) {
+    sInterSliceGCRunner->SetDeadline(aDeadline);
+    runnable = sInterSliceGCRunner;
+  } else {
+    // 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 (ReadyToTriggerExpensiveCollectorTimer()) {
-      CCRunnerFired(TimeStamp());
-    }
-    return;
+    sCCRunner->SetDeadline(aDeadline);
+    runnable = sCCRunner;
   }
 
   if (sICCRunner) {
-    ICCRunnerFired(TimeStamp());
+    sICCRunner->SetDeadline(aDeadline);
+    runnable = sICCRunner;
+  }
+
+  if (runnable) {
+    runnable->Run();
+  }
+}
+
+// static
+void
+nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
+                                        JS::gcreason::Reason aReason)
+{
+  if (!aDocShell || !XRE_IsContentProcess()) {
+    return;
+  }
+
+  nsCOMPtr<nsIDocShellTreeItem> root;
+  aDocShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+  if (root == aDocShell) {
+    // We don't want to run collectors when loading the top level page.
     return;
   }
+
+  nsIDocument* rootDocument = root->GetDocument();
+  if (!rootDocument ||
+      rootDocument->GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE ||
+      rootDocument->IsInBackgroundWindow()) {
+    return;
+  }
+
+  nsIPresShell* presShell = rootDocument->GetShell();
+  if (!presShell) {
+    return;
+  }
+
+  nsViewManager* vm = presShell->GetViewManager();
+  if (!vm) {
+    return;
+  }
+
+  // GetLastUserEventTime returns microseconds.
+  uint32_t lastEventTime = 0;
+  vm->GetLastUserEventTime(lastEventTime);
+  uint32_t currentTime =
+    PR_IntervalToMicroseconds(PR_IntervalNow());
+  // Only try to trigger collectors more often if user hasn't interacted with
+  // the page for awhile.
+  if ((currentTime - lastEventTime) >
+      (NS_USER_INTERACTION_INTERVAL * PR_USEC_PER_MSEC)) {
+    Maybe<TimeStamp> next = nsRefreshDriver::GetNextTickHint();
+    // Try to not delay the next RefreshDriver tick, so give a reasonable
+    // deadline for collectors.
+    if (next.isSome()) {
+      nsJSContext::RunNextCollectorTimer(aReason, next.value());
+    }
+  }
 }
 
 // static
 void
 nsJSContext::PokeGC(JS::gcreason::Reason aReason,
                     JSObject* aObj,
                     int aDelay)
 {
@@ -2404,17 +2438,16 @@ mozilla::dom::StartupJSEnvironment()
   sNeedsFullCC = false;
   sNeedsFullGC = true;
   sNeedsGCAfterCC = false;
   gNameSpaceManager = nullptr;
   sIsInitialized = false;
   sDidShutdown = false;
   sShuttingDown = false;
   gCCStats.Init();
-  sExpensiveCollectorPokes = 0;
 }
 
 static void
 SetGCParameter(JSGCParamKey aParam, uint32_t aValue)
 {
   AutoJSAPI jsapi;
   jsapi.Init();
   JS_SetGCParameter(jsapi.cx(), aParam, aValue);
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -16,16 +16,17 @@
 #include "nsIArray.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 class nsICycleCollectorListener;
 class nsScriptNameSpaceManager;
+class nsIDocShell;
 
 namespace JS {
 class AutoValueVector;
 } // namespace JS
 
 namespace mozilla {
 template <class> class Maybe;
 struct CycleCollectorResults;
@@ -96,17 +97,24 @@ public:
 
   static void BeginCycleCollectionCallback();
   static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults);
 
   // Return the longest CC slice time since ClearMaxCCSliceTime() was last called.
   static uint32_t GetMaxCCSliceTimeSinceClear();
   static void ClearMaxCCSliceTime();
 
-  static void RunNextCollectorTimer();
+  // If there is some pending CC or GC timer/runner, this will run it.
+  static void RunNextCollectorTimer(JS::gcreason::Reason aReason,
+                                    mozilla::TimeStamp aDeadline = mozilla::TimeStamp());
+  // If user has been idle and aDocShell is for an iframe being loaded in an
+  // already loaded top level docshell, this will run a CC or GC
+  // timer/runner if there is such pending.
+  static void MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
+                                         JS::gcreason::Reason aReason);
 
   // 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();
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -102,18 +102,16 @@
 #endif
 
 namespace mozilla {
 
 using namespace dom;
 
 //#define DEBUG_DOCSHELL_FOCUS
 
-#define NS_USER_INTERACTION_INTERVAL 5000 // ms
-
 static const LayoutDeviceIntPoint kInvalidRefPoint = LayoutDeviceIntPoint(-1,-1);
 
 static uint32_t gMouseOrKeyboardEventCounter = 0;
 static nsITimer* gUserInteractionTimer = nullptr;
 static nsITimerCallback* gUserInteractionTimerCallback = nullptr;
 
 static const double kCursorLoadingTimeout = 1000; // ms
 static AutoWeakFrame gLastCursorSourceFrame;
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -13,16 +13,18 @@
 #include "nsWeakReference.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/TimeStamp.h"
 #include "nsIFrame.h"
 #include "Units.h"
 
+#define NS_USER_INTERACTION_INTERVAL 5000 // ms
+
 class nsFrameLoader;
 class nsIContent;
 class nsIDocument;
 class nsIDocShell;
 class nsIDocShellTreeItem;
 class imgIContainer;
 class EnterLeaveDispatcher;
 class nsIContentViewer;
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -105,17 +105,19 @@ namespace JS {
     D(DOM_IPC)                                  \
     D(DOM_WORKER)                               \
     D(INTER_SLICE_GC)                           \
     D(REFRESH_FRAME)                            \
     D(FULL_GC_TIMER)                            \
     D(SHUTDOWN_CC)                              \
     D(UNUSED2)                                  \
     D(USER_INACTIVE)                            \
-    D(XPCONNECT_SHUTDOWN)
+    D(XPCONNECT_SHUTDOWN)                       \
+    D(DOCSHELL)                                 \
+    D(HTML_PARSER)
 
 namespace gcreason {
 
 /* GCReasons will end up looking like JSGC_MAYBEGC */
 enum Reason {
 #define MAKE_REASON(name) name,
     GCREASONS(MAKE_REASON)
 #undef MAKE_REASON
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -13,28 +13,30 @@
 #include "nsIHttpChannel.h"
 #include "nsHtml5Parser.h"
 #include "nsHtml5TreeBuilder.h"
 #include "nsHtml5AtomTable.h"
 #include "nsHtml5Module.h"
 #include "nsHtml5StreamParserPtr.h"
 #include "nsIScriptError.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/SystemGroup.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "nsHtml5Highlighter.h"
 #include "expat_config.h"
 #include "expat.h"
 #include "nsINestedURI.h"
 #include "nsCharsetSource.h"
 #include "nsIWyciwygChannel.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsPrintfCString.h"
 #include "nsNetUtil.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/SchedulerGroup.h"
+#include "nsJSEnvironment.h"
 
 using namespace mozilla;
 
 int32_t nsHtml5StreamParser::sTimerInitialDelay = 120;
 int32_t nsHtml5StreamParser::sTimerSubsequentDelay = 120;
 
 // static
 void
@@ -861,16 +863,34 @@ nsHtml5StreamParser::WriteStreamBytes(co
       MOZ_ASSERT(totalRead == aCount,
                  "The Unicode decoder consumed the wrong number of bytes.");
       *aWriteCount = totalRead;
       return NS_OK;
     }
   }
 }
 
+class MaybeRunCollector : public Runnable
+{
+public:
+  explicit MaybeRunCollector(nsIDocShell* aDocShell)
+    : Runnable("MaybeRunCollector")
+    , mDocShell(aDocShell)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    nsJSContext::MaybeRunNextCollectorSlice(mDocShell, JS::gcreason::HTML_PARSER);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDocShell> mDocShell;
+};
+
 nsresult
 nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   NS_PRECONDITION(STREAM_NOT_STARTED == mStreamState,
                   "Got OnStartRequest when the stream had already started.");
   NS_PRECONDITION(
     !mExecutor->HasStarted(),
     "Got OnStartRequest at the wrong stage in the executor life cycle.");
@@ -966,16 +986,26 @@ nsHtml5StreamParser::OnStartRequest(nsIR
   }
 
   // Attempt to retarget delivery of data (via OnDataAvailable) to the parser
   // thread, rather than through the main thread.
   nsCOMPtr<nsIThreadRetargetableRequest> threadRetargetableRequest =
     do_QueryInterface(mRequest, &rv);
   if (threadRetargetableRequest) {
     rv = threadRetargetableRequest->RetargetDeliveryTo(mEventTarget);
+    if (NS_SUCCEEDED(rv)) {
+      // Parser thread should be now ready to get data from necko and parse it
+      // and main thread might have a chance to process a collector slice.
+      // We need to do this asynchronously so that necko may continue processing
+      // the request.
+      nsCOMPtr<nsIRunnable> runnable =
+        new MaybeRunCollector(mExecutor->GetDocument()->GetDocShell());
+      mozilla::SystemGroup::Dispatch(mozilla::TaskCategory::GarbageCollection,
+                                     runnable.forget());
+    }
   }
 
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to retarget HTML data delivery to the parser thread.");
   }
 
   if (mCharsetSource == kCharsetFromParentFrame) {
     // Remember this in case chardet overwrites mCharsetSource