Test-landing (again) Bug 373462, bug 385322, Better scheduling of cycle collection/gc, r+sr=sicking,jst
authorOlli.Pettay@helsinki.fi
Sun, 21 Oct 2007 07:09:29 -0700
changeset 7078 ae95932f33ba2be0616c6cc894c00d502c3f8b97
parent 7077 4d01978e4897d01887d51a9b1b65fbe933b41d4c
child 7079 fa21071e6b31914526aa7fbfc47b1c402b36fe2c
push idunknown
push userunknown
push dateunknown
bugs373462, 385322
milestone1.9a9pre
Test-landing (again) Bug 373462, bug 385322, Better scheduling of cycle collection/gc, r+sr=sicking,jst
content/base/src/nsXMLHttpRequest.cpp
content/events/src/nsEventStateManager.cpp
dom/src/base/nsJSEnvironment.cpp
dom/src/base/nsJSEnvironment.h
layout/base/nsDocumentViewer.cpp
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollector.h
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -51,16 +51,17 @@
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIEventListenerManager.h"
 #include "nsGUIEvent.h"
 #include "nsIPrivateDOMEvent.h"
 #include "prprf.h"
 #include "nsIDOMEventListener.h"
 #include "nsIJSContextStack.h"
+#include "nsJSEnvironment.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsWeakPtr.h"
 #include "nsICharsetAlias.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDOMClassInfo.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMWindow.h"
 #include "nsIVariant.h"
@@ -1773,16 +1774,17 @@ nsXMLHttpRequest::RequestCompleted()
     NotifyEventListeners(loadEventListeners, domevent);
   }
 
   if (!(mState & XML_HTTP_REQUEST_GOT_FINAL_STOP)) {
     // We're a multipart request, so we're not done. Reset to opened.
     ChangeState(XML_HTTP_REQUEST_OPENED);
   }
 
+  nsJSContext::MaybeCC(PR_FALSE);
   return rv;
 }
 
 NS_IMETHODIMP
 nsXMLHttpRequest::SendAsBinary(const nsAString &aBody)
 {
   char *data = static_cast<char*>(NS_Alloc(aBody.Length() + 1));
   if (!data)
@@ -2315,16 +2317,17 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEv
   mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
 
   ClearEventListeners();
   
   if (event) {
     NotifyEventListeners(errorEventListeners, event);
   }
 
+  nsJSContext::MaybeCC(PR_FALSE);
   return NS_OK;
 }
 
 nsresult
 nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast,
                               PRBool aClearEventListeners)
 {
   // If we are setting one of the mutually exclusive states,
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -131,24 +131,29 @@
 #include "nsContentUtils.h"
 
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsISupportsPrimitives.h"
 #include "nsEventDispatcher.h"
 #include "nsPresShellIterator.h"
 
+#include "nsServiceManagerUtils.h"
+#include "nsITimer.h"
+
 #ifdef XP_MACOSX
 #include <Events.h>
 #endif
 
 #if defined(DEBUG_rods) || defined(DEBUG_bryner)
 //#define DEBUG_DOCSHELL_FOCUS
 #endif
 
+#define NS_USER_INTERACTION_INTERVAL 5000 // ms
+
 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
 
 
 //we will use key binding by default now. this wil lbreak viewer for now
 #define NON_KEYBINDING 0
 
 static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
 
@@ -166,16 +171,51 @@ enum nsTextfieldSelectModel {
 // Which types of elements are in the tab order?
 static PRInt8 sTextfieldSelectModel = eTextfieldSelect_unset;
 static PRBool sLeftClickOnly = PR_TRUE;
 static PRBool sKeyCausesActivation = PR_TRUE;
 static PRUint32 sESMInstanceCount = 0;
 static PRInt32 sChromeAccessModifier = 0, sContentAccessModifier = 0;
 PRInt32 nsEventStateManager::sUserInputEventDepth = 0;
 
+static PRUint32 gMouseOrKeyboardEventCounter = 0;
+static nsITimer* gUserInteractionTimer = nsnull;
+static nsITimerCallback* gUserInteractionTimerCallback = nsnull;
+
+class nsUITimerCallback : public nsITimerCallback
+{
+public:
+  nsUITimerCallback() : mPreviousCount(0) {}
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+private:
+  PRUint32 mPreviousCount;
+};
+
+NS_IMPL_ISUPPORTS1(nsUITimerCallback, nsITimerCallback)
+
+// If aTimer is nsnull, this method always sends "user-interaction-inactive"
+// notification.
+NS_IMETHODIMP
+nsUITimerCallback::Notify(nsITimer* aTimer)
+{
+  nsresult rv;
+  nsCOMPtr<nsIObserverService> obs =
+      do_GetService("@mozilla.org/observer-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
+    gMouseOrKeyboardEventCounter = 0;
+    obs->NotifyObservers(nsnull, "user-interaction-inactive", nsnull);
+  } else {
+    obs->NotifyObservers(nsnull, "user-interaction-active", nsnull);
+  }
+  mPreviousCount = gMouseOrKeyboardEventCounter;
+  return NS_OK;
+}
+
 enum {
  MOUSE_SCROLL_N_LINES,
  MOUSE_SCROLL_PAGE,
  MOUSE_SCROLL_HISTORY,
  MOUSE_SCROLL_TEXTSIZE,
  MOUSE_SCROLL_PIXELS
 };
 
@@ -426,16 +466,28 @@ nsEventStateManager::nsEventStateManager
     mMClickCount(0),
     mRClickCount(0),
     mNormalLMouseEventInProcess(PR_FALSE),
     m_haveShutdown(PR_FALSE),
     mBrowseWithCaret(PR_FALSE),
     mTabbedThroughDocument(PR_FALSE),
     mAccessKeys(nsnull)
 {
+  if (sESMInstanceCount == 0) {
+    gUserInteractionTimerCallback = new nsUITimerCallback();
+    if (gUserInteractionTimerCallback) {
+      NS_ADDREF(gUserInteractionTimerCallback);
+      CallCreateInstance("@mozilla.org/timer;1", &gUserInteractionTimer);
+      if (gUserInteractionTimer) {
+        gUserInteractionTimer->InitWithCallback(gUserInteractionTimerCallback,
+                                                NS_USER_INTERACTION_INTERVAL,
+                                                nsITimer::TYPE_REPEATING_SLACK);
+      }
+    }
+  }
   ++sESMInstanceCount;
 }
 
 NS_IMETHODIMP
 nsEventStateManager::Init()
 {
   nsresult rv;
   nsCOMPtr<nsIObserverService> observerService =
@@ -504,16 +556,24 @@ nsEventStateManager::~nsEventStateManage
 #if CLICK_HOLD_CONTEXT_MENUS
   KillClickHoldTimer();
 #endif
 
   --sESMInstanceCount;
   if(sESMInstanceCount == 0) {
     NS_IF_RELEASE(gLastFocusedContent);
     NS_IF_RELEASE(gLastFocusedDocument);
+    if (gUserInteractionTimerCallback) {
+      gUserInteractionTimerCallback->Notify(nsnull);
+      NS_RELEASE(gUserInteractionTimerCallback);
+    }
+    if (gUserInteractionTimer) {
+      gUserInteractionTimer->Cancel();
+      NS_RELEASE(gUserInteractionTimer);
+    }
   }
 
   delete mAccessKeys;
 
   if (!m_haveShutdown) {
     Shutdown();
 
     // Don't remove from Observer service in Shutdown because Shutdown also
@@ -719,16 +779,31 @@ nsEventStateManager::PreHandleEvent(nsPr
   mCurrentTargetContent = nsnull;
 
   // Focus events don't necessarily need a frame.
   if (NS_EVENT_NEEDS_FRAME(aEvent)) {
     NS_ASSERTION(mCurrentTarget, "mCurrentTarget is null.  this should not happen.  see bug #13007");
     if (!mCurrentTarget) return NS_ERROR_NULL_POINTER;
   }
 
+  if (NS_IS_TRUSTED_EVENT(aEvent) &&
+      ((aEvent->eventStructType == NS_MOUSE_EVENT  &&
+        static_cast<nsMouseEvent*>(aEvent)->reason == nsMouseEvent::eReal) ||
+       aEvent->eventStructType == NS_MOUSE_SCROLL_EVENT ||
+       aEvent->eventStructType == NS_KEY_EVENT)) {
+    if (gMouseOrKeyboardEventCounter == 0) {
+      nsCOMPtr<nsIObserverService> obs =
+        do_GetService("@mozilla.org/observer-service;1");
+      if (obs) {
+        obs->NotifyObservers(nsnull, "user-interaction-active", nsnull);
+      }
+    }
+    ++gMouseOrKeyboardEventCounter;
+  }
+
   *aStatus = nsEventStatus_eIgnore;
 
   nsMouseWheelTransaction::OnEvent(aEvent);
 
   switch (aEvent->message) {
   case NS_MOUSE_BUTTON_DOWN:
     switch (static_cast<nsMouseEvent*>(aEvent)->button) {
     case nsMouseEvent::eLeftButton:
--- a/dom/src/base/nsJSEnvironment.cpp
+++ b/dom/src/base/nsJSEnvironment.cpp
@@ -144,18 +144,36 @@ static PRLogModuleInfo* gJSDiagnostics;
 #define NS_LOAD_IN_PROCESS_GC_DELAY 4000 // ms
 
 // The amount of time we wait from the first request to GC to actually
 // doing the first GC.
 #define NS_FIRST_GC_DELAY           10000 // ms
 
 #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT
 
+// The max number of delayed cycle collects..
+#define NS_MAX_DELAYED_CCOLLECT     45
+// The max number of user interaction notifications in inactive state before
+// we try to call cycle collector more aggressively.
+#define NS_CC_SOFT_LIMIT_INACTIVE   6
+// The max number of user interaction notifications in active state before
+// we try to call cycle collector more aggressively.
+#define NS_CC_SOFT_LIMIT_ACTIVE     12
+// When higher probability MaybeCC is used, the number of sDelayedCCollectCount
+// is multiplied with this number.
+#define NS_PROBABILITY_MULTIPLIER   3
+// Cycle collector should never run more often than this value
+#define NS_MIN_CC_INTERVAL          10000 // ms
+
 // if you add statics here, add them to the list in nsJSRuntime::Startup
 
+static PRUint32 sDelayedCCollectCount;
+static PRUint32 sCCollectCount;
+static PRTime sPreviousCCTime;
+static PRBool sPreviousCCDidCollect;
 static nsITimer *sGCTimer;
 static PRBool sReadyForGC;
 
 // The number of currently pending document loads. This count isn't
 // guaranteed to always reflect reality and can't easily as we don't
 // have an easy place to know when a load ends or is interrupted in
 // all cases. This counter also gets reset if we end up GC'ing while
 // we're waiting for a slow page to load. IOW, this count may be 0
@@ -189,16 +207,85 @@ static PRTime sMaxScriptRunTime;
 static PRTime sMaxChromeScriptRunTime;
 
 static nsIScriptSecurityManager *sSecurityManager;
 
 static nsICollation *gCollation;
 
 static nsIUnicodeDecoder *gDecoder;
 
+// nsUserActivityObserver observes user-interaction-active and
+// user-interaction-inactive notifications. It counts the number of
+// notifications and if the number is bigger than NS_CC_SOFT_LIMIT_ACTIVE
+// (in case the current notification is user-interaction-active) or
+// NS_CC_SOFT_LIMIT_INACTIVE (current notification is user-interaction-inactive)
+// MaybeCC is called with aHigherParameter set to PR_TRUE, otherwise PR_FALSE.
+//
+// When moving from active state to inactive, nsJSContext::CC() is called
+// unless the timer related to page load is active.
+
+class nsUserActivityObserver : public nsIObserver
+{
+public:
+  nsUserActivityObserver()
+  : mUserActivityCounter(0), mOldCCollectCount(0), mUserIsActive(PR_FALSE) {}
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+private:
+  PRUint32 mUserActivityCounter;
+  PRUint32 mOldCCollectCount;
+  PRBool   mUserIsActive;
+};
+
+NS_IMPL_ISUPPORTS1(nsUserActivityObserver, nsIObserver)
+
+NS_IMETHODIMP
+nsUserActivityObserver::Observe(nsISupports* aSubject, const char* aTopic,
+                                const PRUnichar* aData)
+{
+  if (mOldCCollectCount != sCCollectCount) {
+    mOldCCollectCount = sCCollectCount;
+    // Cycle collector was called between user interaction notifications, so
+    // we can reset the counter.
+    mUserActivityCounter = 0;
+  }
+  PRBool higherProbability = PR_FALSE;
+  ++mUserActivityCounter;
+  if (!strcmp(aTopic, "user-interaction-inactive")) {
+#ifdef DEBUG_smaug
+    printf("user-interaction-inactive\n");
+#endif
+    if (mUserIsActive) {
+      mUserIsActive = PR_FALSE;
+      if (!sGCTimer) {
+        nsJSContext::CC();
+        return NS_OK;
+      }
+    }
+    higherProbability = (mUserActivityCounter > NS_CC_SOFT_LIMIT_INACTIVE);
+  } else if (!strcmp(aTopic, "user-interaction-active")) {
+#ifdef DEBUG_smaug
+    printf("user-interaction-active\n");
+#endif
+    mUserIsActive = PR_TRUE;
+    higherProbability = (mUserActivityCounter > NS_CC_SOFT_LIMIT_ACTIVE);
+  } else if (!strcmp(aTopic, "xpcom-shutdown")) {
+    nsCOMPtr<nsIObserverService> obs =
+      do_GetService("@mozilla.org/observer-service;1");
+    if (obs) {
+      obs->RemoveObserver(this, "user-interaction-active");
+      obs->RemoveObserver(this, "user-interaction-inactive");
+      obs->RemoveObserver(this, "xpcom-shutdown");
+    }
+    return NS_OK;
+  }
+  nsJSContext::MaybeCC(higherProbability);
+  return NS_OK;
+}
+
 /****************************************************************
  ************************** AutoFree ****************************
  ****************************************************************/
 
 class AutoFree {
 public:
   AutoFree(void *aPtr) : mPtr(aPtr) {
   }
@@ -3166,16 +3253,90 @@ nsJSContext::ScriptExecuted()
 }
 
 NS_IMETHODIMP
 nsJSContext::PreserveWrapper(nsIXPConnectWrappedNative *aWrapper)
 {
   return nsDOMClassInfo::PreserveNodeWrapper(aWrapper);
 }
 
+//static
+void
+nsJSContext::MaybeCCOrGC(nsIScriptContext* aContext)
+{
+  if (!nsJSContext::MaybeCC(PR_TRUE)) {
+    nsCOMPtr<nsIScriptContext> context = aContext;
+    if (context) {
+      JSContext* cx = static_cast<JSContext*>(context->GetNativeContext());
+      if (cx) {
+#ifdef DEBUG_smaug
+        printf("Will call JS_GC\n");
+#endif
+        ::JS_GC(cx);
+#ifdef DEBUG_smaug
+        printf("Did call JS_GC\n");
+#endif
+      }
+    }
+  }
+}
+
+//static
+void
+nsJSContext::CC()
+{
+  sPreviousCCTime = PR_Now();
+  sDelayedCCollectCount = 0;
+  ++sCCollectCount;
+#ifdef DEBUG_smaug
+  printf("Will run cycle collector (%i)\n", sCCollectCount);
+#endif
+  // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so
+  // we do not explicitly call ::JS_GC() here.
+  PRBool firstRun = nsCycleCollector_collect();
+#ifdef DEBUG_smaug
+  printf("(1) %s\n", firstRun ?
+                     "Cycle collector did collect nodes" :
+                     "Cycle collector did not collect nodes");
+#endif
+  PRBool secondRun = nsCycleCollector_collect();
+#ifdef DEBUG_smaug
+  printf("(2) %s\n", secondRun ?
+                     "Cycle collector did collect nodes" :
+                     "Cycle collector did not collect nodes");
+#endif
+  sPreviousCCDidCollect = firstRun || secondRun;
+}
+
+//static
+PRBool
+nsJSContext::MaybeCC(PRBool aHigherProbability)
+{
+  ++sDelayedCCollectCount;
+  // Increase the probability also if the previous call to cycle collector
+  // collected something.
+  if (aHigherProbability || sPreviousCCDidCollect) {
+    sDelayedCCollectCount *= NS_PROBABILITY_MULTIPLIER;
+  }
+
+  if (!sGCTimer && (sDelayedCCollectCount > NS_MAX_DELAYED_CCOLLECT)) {
+    if ((PR_Now() - sPreviousCCTime) >=
+        PRTime(NS_MIN_CC_INTERVAL * PR_USEC_PER_MSEC)) {
+      nsJSContext::CC();
+      return PR_TRUE;
+    }
+#ifdef DEBUG_smaug
+    else {
+      printf("Running cycle collector was delayed: NS_MIN_CC_INTERVAL\n");
+    }
+#endif
+  }
+  return PR_FALSE;
+}
+
 NS_IMETHODIMP
 nsJSContext::Notify(nsITimer *timer)
 {
   NS_ASSERTION(mContext, "No context in nsJSContext::Notify()!");
 
   NS_RELEASE(sGCTimer);
 
   if (sPendingLoadCount == 0 || sLoadInProgressGCTimer) {
@@ -3184,19 +3345,17 @@ nsJSContext::Notify(nsITimer *timer)
     // Reset sPendingLoadCount in case the timer that fired was a
     // timer we scheduled due to a normal GC timer firing while
     // documents were loading. If this happens we're waiting for a
     // document that is taking a long time to load, and we effectively
     // ignore the fact that the currently loading documents are still
     // loading and move on as if they weren't.
     sPendingLoadCount = 0;
 
-    // nsCycleCollector_collect() will run a ::JS_GC() indirectly,
-    // so we do not explicitly call ::JS_GC() here. 
-    nsCycleCollector_collect();
+    MaybeCCOrGC(this);
   } else {
     FireGCTimer(PR_TRUE);
   }
 
   sReadyForGC = PR_TRUE;
 
   return NS_OK;
 }
@@ -3205,33 +3364,30 @@ nsJSContext::Notify(nsITimer *timer)
 void
 nsJSContext::LoadStart()
 {
   ++sPendingLoadCount;
 }
 
 // static
 void
-nsJSContext::LoadEnd()
+nsJSContext::LoadEnd(nsIScriptGlobalObject* aGlobalObject)
 {
   // sPendingLoadCount is not a well managed load counter (and doesn't
   // need to be), so make sure we don't make it wrap backwards here.
   if (sPendingLoadCount > 0) {
     --sPendingLoadCount;
   }
 
   if (!sPendingLoadCount && sLoadInProgressGCTimer) {
     sGCTimer->Cancel();
-
     NS_RELEASE(sGCTimer);
     sLoadInProgressGCTimer = PR_FALSE;
 
-    // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so
-    // we do not explicitly call ::JS_GC() here.
-    nsCycleCollector_collect();
+    MaybeCCOrGC(aGlobalObject ? aGlobalObject->GetContext() : nsnull);
   }
 }
 
 void
 nsJSContext::FireGCTimer(PRBool aLoadInProgress)
 {
   // Always clear the newborn roots.  If there's already a timer, this
   // will let the GC from that timer clean up properly.  If we're going
@@ -3248,20 +3404,17 @@ nsJSContext::FireGCTimer(PRBool aLoadInP
 
   if (!sGCTimer) {
     NS_WARNING("Failed to create timer");
 
     // Reset sLoadInProgressGCTimer since we're not able to fire the
     // timer.
     sLoadInProgressGCTimer = PR_FALSE;
 
-    // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so
-    // we do not explicitly call ::JS_GC() here.
-    nsCycleCollector_collect();
-
+    MaybeCCOrGC(this);
     return;
   }
 
   static PRBool first = PR_TRUE;
 
   sGCTimer->InitWithCallback(this,
                              first ? NS_FIRST_GC_DELAY :
                              aLoadInProgress ? NS_LOAD_IN_PROCESS_GC_DELAY :
@@ -3359,16 +3512,20 @@ nsJSRuntime::ParseVersion(const nsString
     return NS_OK;
 }
 
 //static
 void
 nsJSRuntime::Startup()
 {
   // initialize all our statics, so that we can restart XPCOM
+  sDelayedCCollectCount = 0;
+  sCCollectCount = 0;
+  sPreviousCCTime = 0;
+  sPreviousCCDidCollect = PR_FALSE;
   sGCTimer = nsnull;
   sReadyForGC = PR_FALSE;
   sLoadInProgressGCTimer = PR_FALSE;
   sPendingLoadCount = 0;
   gNameSpaceManager = nsnull;
   sRuntimeService = nsnull;
   sRuntime = nsnull;
   gOldJSGCCallback = nsnull;
@@ -3477,16 +3634,25 @@ nsJSRuntime::Init()
   MaxScriptRunTimePrefChangedCallback("dom.max_script_run_time", nsnull);
 
   nsContentUtils::RegisterPrefCallback("dom.max_chrome_script_run_time",
                                        MaxScriptRunTimePrefChangedCallback,
                                        nsnull);
   MaxScriptRunTimePrefChangedCallback("dom.max_chrome_script_run_time",
                                       nsnull);
 
+  nsCOMPtr<nsIObserverService> obs =
+    do_GetService("@mozilla.org/observer-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsIObserver* activityObserver = new nsUserActivityObserver();
+  NS_ENSURE_TRUE(activityObserver, NS_ERROR_OUT_OF_MEMORY);
+  obs->AddObserver(activityObserver, "user-interaction-inactive", PR_FALSE);
+  obs->AddObserver(activityObserver, "user-interaction-active", PR_FALSE);
+  obs->AddObserver(activityObserver, "xpcom-shutdown", PR_FALSE);
+
   rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &sSecurityManager);
 
   sIsInitialized = NS_SUCCEEDED(rv);
 
   return rv;
 }
 
 void nsJSRuntime::ShutDown()
--- a/dom/src/base/nsJSEnvironment.h
+++ b/dom/src/base/nsJSEnvironment.h
@@ -163,17 +163,36 @@ public:
   virtual nsresult DropScriptObject(void *object);
   virtual nsresult HoldScriptObject(void *object);
 
   NS_DECL_NSIXPCSCRIPTNOTIFY
 
   NS_DECL_NSITIMERCALLBACK
 
   static void LoadStart();
-  static void LoadEnd();
+  static void LoadEnd(nsIScriptGlobalObject* aGlobalObject);
+
+  // CC does always call cycle collector and it also updates the counters
+  // that MaybeCC uses.
+  static void CC();
+
+  // MaybeCC calls cycle collector if certain conditions are fulfilled.
+  // The conditions are:
+  // - The timer related to page load (sGCTimer) must not be active.
+  // - At least NS_MIN_CC_INTERVAL milliseconds must have elapsed since the
+  //   previous cycle collector call.
+  // - Certain number of MaybeCC calls have occurred.
+  //   The number of needed MaybeCC calls depends on the aHigherProbability
+  //   parameter. If the parameter is true, probability for calling cycle
+  //   collector rises increasingly. If the parameter is all the time false,
+  //   at least NS_MAX_DELAYED_CCOLLECT MaybeCC calls are needed.
+  //   If the previous call to cycle collector did collect something,
+  //   MaybeCC works effectively as if aHigherProbability was true.
+  // @return PR_TRUE if cycle collector was called.
+  static PRBool MaybeCC(PRBool aHigherProbability);
 
 protected:
   nsresult InitializeExternalClasses();
   // aHolder should be holding our global object
   nsresult FindXPCNativeWrapperClass(nsIXPConnectJSObjectHolder *aHolder);
 
   // Helper to convert xpcom datatypes to jsvals.
   nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
@@ -185,16 +204,18 @@ protected:
 
   void FireGCTimer(PRBool aLoadInProgress);
 
   // given an nsISupports object (presumably an event target or some other
   // DOM object), get (or create) the JSObject wrapping it.
   nsresult JSObjectFromInterface(nsISupports *aSup, void *aScript, 
                                  JSObject **aRet);
 
+  static void MaybeCCOrGC(nsIScriptContext* aContext);
+
 private:
   JSContext *mContext;
   PRUint32 mNumEvaluations;
 
 protected:
   struct TerminationFuncHolder;
   friend struct TerminationFuncHolder;
   
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -988,17 +988,17 @@ DocumentViewerImpl::LoadComplete(nsresul
 
   // Now that the document has loaded, we can tell the presshell
   // to unsuppress painting.
   if (mPresShell && !mStopped) {
     nsCOMPtr<nsIPresShell> shellDeathGrip(mPresShell); // bug 378682
     mPresShell->UnsuppressPainting();
   }
 
-  nsJSContext::LoadEnd();
+  nsJSContext::LoadEnd(mDocument ? mDocument->GetScriptGlobalObject() : nsnull);
 
 #ifdef NS_PRINTING
   // Check to see if someone tried to print during the load
   if (mPrintIsPending) {
     mPrintIsPending        = PR_FALSE;
     mPrintDocIsFullyLoaded = PR_TRUE;
     Print(mCachedPrintSettings, mCachedPrintWebProgressListner);
     mCachedPrintSettings           = nsnull;
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -864,17 +864,17 @@ struct nsCycleCollector
 
     nsCycleCollector();
     ~nsCycleCollector();
 
     PRBool Suspect(nsISupports *n, PRBool current = PR_FALSE);
     PRBool Forget(nsISupports *n);
     void Allocated(void *n, size_t sz);
     void Freed(void *n);
-    void Collect(PRUint32 aTryCollections = 1);
+    PRBool Collect(PRUint32 aTryCollections = 1);
     void Shutdown();
 
 #ifdef DEBUG_CC
     nsCycleCollectorStats mStats;    
 
     FILE *mPtrLog;
 
     void MaybeDrawGraphs(GCGraph &graph);
@@ -2016,27 +2016,28 @@ nsCycleCollector::Freed(void *n)
             if (!mPtrLog)
                 mPtrLog = fopen("pointer_log", "w");
             fprintf(mPtrLog, "R %p\n", n);
         }
     }
 }
 #endif
 
-void
+PRBool
 nsCycleCollector::Collect(PRUint32 aTryCollections)
 {
+    PRBool didCollect = PR_FALSE;
 #if defined(DEBUG_CC) && !defined(__MINGW32__)
     if (!mParams.mDoNothing && mParams.mHookMalloc)
         InitMemHook();
 #endif
 
     // This can legitimately happen in a few cases. See bug 383651.
     if (mCollectionInProgress)
-        return;
+        return didCollect;
 
 #ifdef COLLECT_TIME_DEBUG
     printf("cc: Starting nsCycleCollector::Collect(%d)\n", aTryCollections);
     PRTime start = PR_Now(), now;
 #endif
 
     mCollectionInProgress = PR_TRUE;
 
@@ -2163,18 +2164,21 @@ nsCycleCollector::Collect(PRUint32 aTryC
 
                 --aTryCollections;
 
                 // Since runtimes may add wrappers to the purple buffer
                 // (which will mean we won't stop repeating due to the
                 // mBuf.GetSize() == 0 check above), we should stop
                 // repeating collections if we didn't collect anything
                 // this time.
-                if (!collected)
+                if (!collected) {
                     aTryCollections = 0;
+                } else {
+                    didCollect = PR_TRUE;
+                }
             }
 
 #ifdef DEBUG_CC
             mStats.mCollection++;
             if (mParams.mReportStats)
                 mStats.Dump();
 #endif
         }
@@ -2189,16 +2193,17 @@ nsCycleCollector::Collect(PRUint32 aTryC
 
 #ifdef COLLECT_TIME_DEBUG
     printf("cc: Collect() took %lldms\n",
            (PR_Now() - start) / PR_USEC_PER_MSEC);
 #endif
 #ifdef DEBUG_CC
     ExplainLiveExpectedGarbage();
 #endif
+    return didCollect;
 }
 
 void
 nsCycleCollector::Shutdown()
 {
     // Here we want to run a final collection on everything we've seen
     // buffered, irrespective of age; then permanently disable
     // the collector because the program is shutting down.
@@ -2589,21 +2594,20 @@ PRBool
 NS_CycleCollectorForget(nsISupports *n)
 {
     if (sCollector)
         return sCollector->Forget(n);
     return PR_FALSE;
 }
 
 
-void 
+PRBool
 nsCycleCollector_collect()
 {
-    if (sCollector)
-        sCollector->Collect();
+    return sCollector ? sCollector->Collect() : PR_FALSE;
 }
 
 nsresult 
 nsCycleCollector_startup()
 {
     NS_ASSERTION(!sCollector, "Forgot to call nsCycleCollector_shutdown?");
 
     sCollector = new nsCycleCollector();
--- a/xpcom/base/nsCycleCollector.h
+++ b/xpcom/base/nsCycleCollector.h
@@ -61,17 +61,18 @@ struct nsCycleCollectionLanguageRuntime
     virtual void SuspectExtraPointers() = 0;
 #endif
 };
 
 // PRBool nsCycleCollector_suspect(nsISupports *n);
 NS_COM void nsCycleCollector_suspectCurrent(nsISupports *n);
 // NS_COM PRBool nsCycleCollector_forget(nsISupports *n);
 nsresult nsCycleCollector_startup();
-NS_COM void nsCycleCollector_collect();
+// Returns PR_TRUE if some nodes were collected.
+NS_COM PRBool nsCycleCollector_collect();
 void nsCycleCollector_shutdown();
 
 #ifdef DEBUG
 NS_COM void nsCycleCollector_DEBUG_shouldBeFreed(nsISupports *n);
 NS_COM void nsCycleCollector_DEBUG_wasFreed(nsISupports *n);
 #endif
 
 // Helpers for interacting with language-identified scripts