Substitute operation counting with a watchdog thread (477187, 2nd attempt, r=brendan/mrbkap/jst, sr=brendan/jst).
authorAndreas Gal <gal@mozilla.com>
Tue, 10 Feb 2009 03:45:36 -0800
changeset 24884 08950e8b525426daaa49e283b07a4e19635ec048
parent 24883 a2334889e2f5aafff577fa1246ce11e17fddc860
child 24885 8590bf3b592cf98ea0b7d3e9da316fd75e5309d5
child 25086 fdfbd9cfc5fc756709ff09b622f29da6c26f17a7
push idunknown
push userunknown
push dateunknown
reviewersbrendan, mrbkap, jst, brendan, jst
bugs477187
milestone1.9.2a1pre
Substitute operation counting with a watchdog thread (477187, 2nd attempt, r=brendan/mrbkap/jst, sr=brendan/jst).
dom/src/base/nsJSEnvironment.cpp
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMThreadService.h
dom/src/threads/nsDOMWorker.cpp
dom/src/threads/nsDOMWorker.h
dom/src/threads/nsDOMWorkerPool.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsarray.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsdate.cpp
js/src/jsdate.h
js/src/jsgc.cpp
js/src/jsinterp.cpp
js/src/jslock.cpp
js/src/jslock.h
js/src/jsobj.cpp
js/src/jspubtd.h
js/src/jsregexp.cpp
js/src/jsscope.cpp
js/src/jstracer.cpp
js/src/shell/js.cpp
js/src/xpconnect/src/xpccomponents.cpp
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcprivate.h
--- a/dom/src/base/nsJSEnvironment.cpp
+++ b/dom/src/base/nsJSEnvironment.cpp
@@ -849,19 +849,16 @@ PrintWinCodebase(nsGlobalWindow *win)
   }
 
   nsCAutoString spec;
   uri->GetSpec(spec);
   printf("%s\n", spec.get());
 }
 #endif
 
-// The accumulated operation weight before we call MaybeGC
-const PRUint32 MAYBE_GC_OPERATION_WEIGHT = 5000 * JS_OPERATION_WEIGHT_BASE;
-
 static void
 MaybeGC(JSContext *cx)
 {
   size_t bytes = cx->runtime->gcBytes;
   size_t lastBytes = cx->runtime->gcLastBytes;
 
   if ((bytes > 8192 && bytes / 16 > lastBytes)
 #ifdef DEBUG
@@ -922,17 +919,17 @@ nsJSContext::DOMOperationCallback(JSCont
 
   PRBool lowMemory;
   mem->IsLowMemory(&lowMemory);
   if (lowMemory) {
     // try to clean up:
     nsJSContext::CC();
 
     // never prevent system scripts from running
-    if (! ::JS_IsSystemObject(cx, ::JS_GetGlobalObject(cx))) {
+    if (!::JS_IsSystemObject(cx, ::JS_GetGlobalObject(cx))) {
 
       // lets see if CC() did anything, if not, cancel the script.
       mem->IsLowMemory(&lowMemory);
       if (lowMemory) {
 
         if (nsContentUtils::GetBoolPref("dom.prevent_oom_dialog", PR_FALSE))
           return JS_FALSE;
         
@@ -983,17 +980,17 @@ nsJSContext::DOMOperationCallback(JSCont
   // If we get here we're most likely executing an infinite loop in JS,
   // we'll tell the user about this and we'll give the user the option
   // of stopping the execution of the script.
   nsCOMPtr<nsIPrompt> prompt = GetPromptFromContext(ctx);
   NS_ENSURE_TRUE(prompt, JS_TRUE);
 
   // Check if we should offer the option to debug
   JSStackFrame* fp = ::JS_GetScriptedCaller(cx, NULL);
-  PRBool debugPossible = (fp != nsnull &&
+  PRBool debugPossible = (fp != nsnull && cx->debugHooks &&
                           cx->debugHooks->debuggerHandler != nsnull);
 #ifdef MOZ_JSDEBUGGER
   // Get the debugger service if necessary.
   if (debugPossible) {
     PRBool jsds_IsOn = PR_FALSE;
     const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
     nsCOMPtr<jsdIExecutionHook> jsdHook;
     nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
@@ -1092,21 +1089,26 @@ nsJSContext::DOMOperationCallback(JSCont
   PRBool neverShowDlgChk = PR_FALSE;
   PRUint32 buttonFlags = (nsIPrompt::BUTTON_TITLE_IS_STRING *
                           (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
 
   // Add a third button if necessary:
   if (debugPossible)
     buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
 
+  // Null out the operation callback while we're re-entering JS here.
+  ::JS_SetOperationCallback(cx, nsnull);
+
   // Open the dialog.
   rv = prompt->ConfirmEx(title, msg, buttonFlags, stopButton, waitButton,
                          debugButton, neverShowDlg, &neverShowDlgChk,
                          &buttonPressed);
 
+  ::JS_SetOperationCallback(cx, DOMOperationCallback);
+
   if (NS_FAILED(rv) || (buttonPressed == 1)) {
     // Allow the script to continue running
 
     if (neverShowDlgChk) {
       nsIPrefBranch *prefBranch = nsContentUtils::GetPrefBranch();
 
       if (prefBranch) {
         prefBranch->SetIntPref(isTrackingChromeCodeTime ?
@@ -1244,18 +1246,17 @@ nsJSContext::nsJSContext(JSRuntime *aRun
     // Make sure the new context gets the default context options
     ::JS_SetOptions(mContext, mDefaultJSOptions);
 
     // Watch for the JS boolean options
     nsContentUtils::RegisterPrefCallback(js_options_dot_str,
                                          JSOptionChangedCallback,
                                          this);
 
-    ::JS_SetOperationCallback(mContext, DOMOperationCallback,
-                              MAYBE_GC_OPERATION_WEIGHT);
+    ::JS_SetOperationCallback(mContext, DOMOperationCallback);
 
     static JSLocaleCallbacks localeCallbacks =
       {
         LocaleToUpperCase,
         LocaleToLowerCase,
         LocaleCompare,
         LocaleToUnicode
       };
@@ -1297,19 +1298,16 @@ void
 nsJSContext::Unlink()
 {
   if (!mContext)
     return;
 
   // Clear our entry in the JSContext, bugzilla bug 66413
   ::JS_SetContextPrivate(mContext, nsnull);
 
-  // Clear the operation callback, bugzilla bug 238218
-  ::JS_ClearOperationCallback(mContext);
-
   // Unregister our "javascript.options.*" pref-changed callback.
   nsContentUtils::UnregisterPrefCallback(js_options_dot_str,
                                          JSOptionChangedCallback,
                                          this);
 
   // Release mGlobalWrapperRef before the context is destroyed
   mGlobalWrapperRef = nsnull;
 
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -100,20 +100,16 @@ PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS 
 // As we suspend threads for various reasons (navigating away from the page,
 // loading scripts, etc.) we open another slot in the thread pool for another
 // worker to use. We can't do this forever so we set an absolute cap on the
 // number of threads we'll allow to prevent DOS attacks.
 #define THREADPOOL_THREAD_CAP 20
 
 PR_STATIC_ASSERT(THREADPOOL_THREAD_CAP >= THREADPOOL_MAX_THREADS);
 
-// The number of times our JS operation callback will be called before yielding
-// the thread
-#define CALLBACK_YIELD_THRESHOLD 100
-
 // A "bad" value for the NSPR TLS functions.
 #define BAD_TLS_INDEX (PRUintn)-1
 
 // Easy access for static functions. No reference here.
 static nsDOMThreadService* gDOMThreadService = nsnull;
 
 // These pointers actually carry references and must be released.
 static nsIObserverService* gObserverService = nsnull;
@@ -321,17 +317,17 @@ public:
     NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
 
     NS_ASSERTION(!JS_GetGlobalObject(cx), "Shouldn't have a global!");
 
     JS_SetContextPrivate(cx, mWorker);
 
     // Tell the worker which context it will be using
     if (mWorker->SetGlobalForContext(cx)) {
-      RunQueue();
+      RunQueue(cx);
 
       // Remove the global object from the context so that it might be garbage
       // collected.
       JS_SetGlobalObject(cx, NULL);
       JS_SetContextPrivate(cx, NULL);
     }
     else {
       // This is usually due to a parse error in the worker script...
@@ -343,19 +339,18 @@ public:
       mon.NotifyAll();
     }
 
     return NS_OK;
   }
 
 protected:
 
-  void RunQueue() {
-    JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
-    NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
+  void RunQueue(JSContext* aCx) {
+    PRBool operationCallbackTriggered = PR_FALSE;
 
     while (1) {
       nsCOMPtr<nsIRunnable> runnable;
       {
         nsAutoMonitor mon(gDOMThreadService->mMonitor);
 
         runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront());
 
@@ -367,18 +362,27 @@ protected:
           }
 #endif
           gDOMThreadService->WorkerComplete(this);
           mon.NotifyAll();
           return;
         }
       }
 
+      if (!operationCallbackTriggered) {
+        // Make sure that our operation callback is set to run before starting.
+        // That way we are sure to suspend this worker if needed.
+        JS_TriggerOperationCallback(aCx);
+
+        // Only need to do this the first time.
+        operationCallbackTriggered = PR_TRUE;
+      }
+
       // Clear out any old cruft hanging around in the regexp statics.
-      JS_ClearRegExpStatics(cx);
+      JS_ClearRegExpStatics(aCx);
 
       runnable->Run();
     }
   }
 
   // Set at construction
   nsRefPtr<nsDOMWorker> mWorker;
 
@@ -398,50 +402,50 @@ DOMWorkerOperationCallback(JSContext* aC
   // Want a strong ref here to make sure that the monitor we wait on won't go
   // away.
   nsRefPtr<nsDOMWorkerPool> pool;
 
   PRBool wasSuspended = PR_FALSE;
   PRBool extraThreadAllowed = PR_FALSE;
   jsrefcount suspendDepth = 0;
 
-  while (1) {
+  do {
     // Kill execution if we're canceled.
     if (worker->IsCanceled()) {
       LOG(("Forcefully killing JS for worker [0x%p]",
            static_cast<void*>(worker)));
 
       if (wasSuspended) {
         if (extraThreadAllowed) {
           gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
         }
         JS_ResumeRequest(aCx, suspendDepth);
       }
 
       // Kill exectuion of the currently running JS.
-      return PR_FALSE;
+      return JS_FALSE;
     }
 
     // Break out if we're not suspended.
     if (!worker->IsSuspended()) {
       if (wasSuspended) {
         if (extraThreadAllowed) {
           gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
         }
         JS_ResumeRequest(aCx, suspendDepth);
       }
-      break;
+      return JS_TRUE;
     }
 
     if (!wasSuspended) {
       // Make sure we can get the monitor we need to wait on. It's possible that
       // the worker was canceled since we checked above.
       if (worker->IsCanceled()) {
         NS_WARNING("Tried to suspend on a pool that has gone away");
-        return PR_FALSE;
+        return JS_FALSE;
       }
 
       pool = worker->Pool();
 
       // Make sure to suspend our request while we block like this, otherwise we
       // prevent GC for everyone.
       suspendDepth = JS_SuspendRequest(aCx);
 
@@ -452,30 +456,20 @@ DOMWorkerOperationCallback(JSContext* aC
         NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1));
 
       // Only do all this setup once.
       wasSuspended = PR_TRUE;
     }
 
     nsAutoMonitor mon(pool->Monitor());
     mon.Wait();
-  }
+  } while (1);
 
-  // Since only one thread can access a context at once we don't have to worry
-  // about atomically incrementing this counter
-  if (++worker->mCallbackCount >= CALLBACK_YIELD_THRESHOLD) {
-    // Must call this so that GC can happen on the main thread!
-    JS_YieldRequest(aCx);
-
-    // Start the counter over.
-    worker->mCallbackCount = 0;
-  }
-
-  // Continue execution.
-  return JS_TRUE;
+  NS_NOTREACHED("Shouldn't get here!");
+  return JS_FALSE;
 }
 
 void
 DOMWorkerErrorReporter(JSContext* aCx,
                        const char* aMessage,
                        JSErrorReport* aReport)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Huh?!");
@@ -628,16 +622,19 @@ nsDOMThreadService::Init()
   NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
 
   PRBool success = mWorkersInProgress.Init();
   NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
 
   success = mPools.Init();
   NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
 
+  success = mJSContexts.SetCapacity(THREADPOOL_THREAD_CAP);
+  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
   nsCOMPtr<nsIJSRuntimeService>
     runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
   NS_ENSURE_TRUE(runtimeSvc, NS_ERROR_FAILURE);
   runtimeSvc.forget(&gJSRuntimeService);
 
   nsCOMPtr<nsIThreadJSContextStack>
     contextStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1"));
   NS_ENSURE_TRUE(contextStack, NS_ERROR_FAILURE);
@@ -833,18 +830,17 @@ nsDOMThreadService::CreateJSContext()
   gJSRuntimeService->GetRuntime(&rt);
   NS_ENSURE_TRUE(rt, nsnull);
 
   JSAutoContextDestroyer cx(JS_NewContext(rt, 8192));
   NS_ENSURE_TRUE(cx, nsnull);
 
   JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
 
-  JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
-                          100 * JS_OPERATION_WEIGHT_BASE);
+  JS_SetOperationCallback(cx, DOMWorkerOperationCallback);
 
   static JSSecurityCallbacks securityCallbacks = {
     nsDOMWorkerSecurityManager::JSCheckAccess,
     nsDOMWorkerSecurityManager::JSTranscodePrincipals,
     nsDOMWorkerSecurityManager::JSFindPrincipal
   };
 
   JS_SetContextSecurityCallbacks(cx, &securityCallbacks);
@@ -867,16 +863,18 @@ nsDOMThreadService::CreateJSContext()
   stackLimit = (currentStackAddr + kStackSize > currentStackAddr) ?
                currentStackAddr + kStackSize :
                (jsuword) -1;
 #endif
 
   JS_SetThreadStackLimit(cx, stackLimit);
   JS_SetScriptStackQuota(cx, 100*1024*1024);
 
+  JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_JIT | JSOPTION_ANONFUNFIX);
+
   return cx.forget();
 }
 
 already_AddRefed<nsDOMWorkerPool>
 nsDOMThreadService::GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
                                      PRBool aRemove)
 {
   NS_ASSERTION(aGlobalObject, "Null pointer!");
@@ -889,45 +887,65 @@ nsDOMThreadService::GetPoolForGlobal(nsI
   if (aRemove) {
     mPools.Remove(aGlobalObject);
   }
 
   return pool.forget();
 }
 
 void
+nsDOMThreadService::TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool)
+{
+  nsAutoMonitor mon(mMonitor);
+
+  // See if we need to trigger the operation callback on any currently running
+  // contexts.
+  PRUint32 contextCount = mJSContexts.Length();
+  for (PRUint32 index = 0; index < contextCount; index++) {
+    JSContext*& cx = mJSContexts[index];
+    nsDOMWorker* worker = (nsDOMWorker*)JS_GetContextPrivate(cx);
+    if (worker && worker->Pool() == aPool) {
+      JS_TriggerOperationCallback(cx);
+    }
+  }
+}
+
+void
 nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
 {
   NS_ASSERTION(aGlobalObject, "Null pointer!");
 
   nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_TRUE);
   if (pool) {
     pool->Cancel();
+    TriggerOperationCallbackForPool(pool);
   }
 }
 
 void
 nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
 {
   NS_ASSERTION(aGlobalObject, "Null pointer!");
 
   nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_FALSE);
   if (pool) {
     pool->Suspend();
+    TriggerOperationCallbackForPool(pool);
   }
 }
 
 void
 nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
 {
   NS_ASSERTION(aGlobalObject, "Null pointer!");
 
   nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_FALSE);
   if (pool) {
     pool->Resume();
+    TriggerOperationCallbackForPool(pool);
   }
 }
 
 void
 nsDOMThreadService::NoteEmptyPool(nsDOMWorkerPool* aPool)
 {
   NS_ASSERTION(aPool, "Null pointer!");
 
@@ -963,16 +981,27 @@ nsDOMThreadService::ChangeThreadPoolMaxT
   if (newThreadCount > THREADPOOL_THREAD_CAP) {
     NS_WARNING("Thread pool cap reached!");
     return NS_ERROR_FAILURE;
   }
 
   rv = mThreadPool->SetThreadLimit((PRUint32)newThreadCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // If we're allowing an extra thread then post a dummy event to the thread
+  // pool so that any pending workers can get started. The thread pool doesn't
+  // do this on its own like it probably should...
+  if (aDelta == 1) {
+    nsCOMPtr<nsIRunnable> dummy(new nsRunnable());
+    if (dummy) {
+      rv = mThreadPool->Dispatch(dummy, NS_DISPATCH_NORMAL);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
   return NS_OK;
 }
 
 nsIJSRuntimeService*
 nsDOMThreadService::JSRuntimeService()
 {
   return gJSRuntimeService;
 }
@@ -1063,16 +1092,26 @@ nsDOMThreadService::OnThreadCreated()
     NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
 
     PRStatus status = PR_SetThreadPrivate(gJSContextIndex, cx);
     if (status != PR_SUCCESS) {
       NS_WARNING("Failed to set context on thread!");
       nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
       return NS_ERROR_FAILURE;
     }
+
+    nsAutoMonitor mon(mMonitor);
+
+#ifdef DEBUG
+    JSContext** newContext =
+#endif
+    mJSContexts.AppendElement(cx);
+
+    // We ensure the capacity of this array in Init.
+    NS_ASSERTION(newContext, "Should never fail!");
   }
 
   // Make sure that XPConnect knows about this context.
   gThreadJSContextStack->Push(cx);
   gThreadJSContextStack->SetSafeJSContext(cx);
 
   return NS_OK;
 }
@@ -1082,16 +1121,21 @@ nsDOMThreadService::OnThreadShuttingDown
 {
   LOG(("Thread shutting down"));
 
   NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
 
   JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
   NS_WARN_IF_FALSE(cx, "Thread died with no context?");
   if (cx) {
+    {
+      nsAutoMonitor mon(mMonitor);
+      mJSContexts.RemoveElement(cx);
+    }
+
     JSContext* pushedCx;
     gThreadJSContextStack->Pop(&pushedCx);
     NS_ASSERTION(pushedCx == cx, "Popped the wrong context!");
 
     gThreadJSContextStack->SetSafeJSContext(nsnull);
 
     nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
   }
--- a/dom/src/threads/nsDOMThreadService.h
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -129,16 +129,18 @@ private:
   void WorkerComplete(nsDOMWorkerRunnable* aRunnable);
 
   static JSContext* CreateJSContext();
 
   already_AddRefed<nsDOMWorkerPool>
     GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
                      PRBool aRemove);
 
+  void TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool);
+
   void NoteEmptyPool(nsDOMWorkerPool* aPool);
 
   void TimeoutReady(nsDOMWorkerTimeout* aTimeout);
 
   nsresult RegisterWorker(nsDOMWorker* aWorker,
                           nsIScriptGlobalObject* aGlobalObject);
 
   void GetAppName(nsAString& aAppName);
@@ -154,16 +156,20 @@ private:
 
   // mMonitor protects all access to mWorkersInProgress and
   // mCreationsInProgress.
   PRMonitor* mMonitor;
 
   // A map from nsDOMWorkerThread to nsDOMWorkerRunnable.
   nsRefPtrHashtable<nsVoidPtrHashKey, nsDOMWorkerRunnable> mWorkersInProgress;
 
+  // A list of active JSContexts that we've created. Always protected with
+  // mMonitor.
+  nsTArray<JSContext*> mJSContexts;
+
   nsString mAppName;
   nsString mAppVersion;
   nsString mPlatform;
   nsString mUserAgent;
 
   PRBool mNavigatorStringsLoaded;
 };
 
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -946,17 +946,16 @@ NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorker
 NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerClassInfo)
 
 static nsDOMWorkerClassInfo sDOMWorkerClassInfo;
 
 nsDOMWorker::nsDOMWorker(nsDOMWorker* aParent,
                          nsIXPConnectWrappedNative* aParentWN)
 : mParent(aParent),
   mParentWN(aParentWN),
-  mCallbackCount(0),
   mLock(nsnull),
   mInnerScope(nsnull),
   mGlobal(NULL),
   mNextTimeoutId(0),
   mFeatureSuspendDepth(0),
   mWrappedNative(nsnull),
   mErrorHandlerRecursionCount(0),
   mCanceled(PR_FALSE),
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -212,18 +212,16 @@ private:
 
 private:
 
   // mParent will live as long as mParentWN but only mParentWN will keep the JS
   // reflection alive, so we only hold one strong reference to mParentWN.
   nsDOMWorker* mParent;
   nsCOMPtr<nsIXPConnectWrappedNative> mParentWN;
 
-  PRUint32 mCallbackCount;
-
   PRLock* mLock;
 
   nsRefPtr<nsDOMWorkerMessageHandler> mInnerHandler;
   nsRefPtr<nsDOMWorkerMessageHandler> mOuterHandler;
 
   nsRefPtr<nsDOMWorkerPool> mPool;
 
   nsDOMWorkerScope* mInnerScope;
--- a/dom/src/threads/nsDOMWorkerPool.cpp
+++ b/dom/src/threads/nsDOMWorkerPool.cpp
@@ -159,31 +159,32 @@ nsDOMWorkerPool::GetWorkers(nsTArray<nsD
 }
 
 void
 nsDOMWorkerPool::Cancel()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mCanceled, "Canceled more than once!");
 
+  nsAutoTArray<nsDOMWorker*, 10> workers;
   {
     nsAutoMonitor mon(mMonitor);
 
     mCanceled = PR_TRUE;
 
-    nsAutoTArray<nsDOMWorker*, 10> workers;
     GetWorkers(workers);
+  }
 
-    PRUint32 count = workers.Length();
-    if (count) {
-      for (PRUint32 index = 0; index < count; index++) {
-        workers[index]->Cancel();
-      }
-      mon.NotifyAll();
+  PRUint32 count = workers.Length();
+  if (count) {
+    for (PRUint32 index = 0; index < count; index++) {
+      workers[index]->Cancel();
     }
+    nsAutoMonitor mon(mMonitor);
+    mon.NotifyAll();
   }
 
   mParentGlobal = nsnull;
   mParentDocument = nsnull;
 }
 
 void
 nsDOMWorkerPool::Suspend()
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1853,34 +1853,32 @@ JS_ComputeThis(JSContext *cx, jsval *vp)
 }
 
 JS_PUBLIC_API(void *)
 JS_malloc(JSContext *cx, size_t nbytes)
 {
     void *p;
 
     JS_ASSERT(nbytes != 0);
-    JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
     if (nbytes == 0)
         nbytes = 1;
 
     p = malloc(nbytes);
     if (!p) {
         JS_ReportOutOfMemory(cx);
         return NULL;
     }
     js_UpdateMallocCounter(cx, nbytes);
 
     return p;
 }
 
 JS_PUBLIC_API(void *)
 JS_realloc(JSContext *cx, void *p, size_t nbytes)
 {
-    JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
     p = realloc(p, nbytes);
     if (!p)
         JS_ReportOutOfMemory(cx);
     return p;
 }
 
 JS_PUBLIC_API(void)
 JS_free(JSContext *cx, void *p)
@@ -5299,104 +5297,43 @@ JS_CallFunctionValue(JSContext *cx, JSOb
     JSBool ok;
 
     CHECK_REQUEST(cx);
     ok = js_InternalCall(cx, obj, fval, argc, argv, rval);
     LAST_FRAME_CHECKS(cx, ok);
     return ok;
 }
 
-JS_PUBLIC_API(void)
-JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
-                        uint32 operationLimit)
-{
-    JS_SetOperationCallbackFunction(cx, callback);
-    JS_SetOperationLimit(cx, operationLimit);
-}
-
-JS_PUBLIC_API(void)
-JS_ClearOperationCallback(JSContext *cx)
-{
-    JS_SetOperationCallbackFunction(cx, NULL);
-    JS_SetOperationLimit(cx, JS_MAX_OPERATION_LIMIT);
-}
-
-JS_PUBLIC_API(void)
-JS_SetOperationLimit(JSContext *cx, uint32 operationLimit)
-{
-    /* Mixed operation and branch callbacks are not supported. */
-    JS_ASSERT(!cx->branchCallbackWasSet);
-    JS_ASSERT(operationLimit <= JS_MAX_OPERATION_LIMIT);
-    JS_ASSERT(operationLimit > 0);
-
-    cx->operationCount = (int32) operationLimit;
-    cx->operationLimit = operationLimit;
-}
-
-JS_PUBLIC_API(uint32)
-JS_GetOperationLimit(JSContext *cx)
-{
-    JS_ASSERT(!cx->branchCallbackWasSet);
-
-    /*
-     * cx->operationLimit is initialized to JS_MAX_OPERATION_LIMIT + 1 to
-     * detect for optimizations if the embedding has ever set it.
-     */
-    JS_ASSERT(cx->operationLimit <= JS_MAX_OPERATION_LIMIT + 1);
-    return JS_MIN(cx->operationLimit, JS_MAX_OPERATION_LIMIT);
-}
-
-JS_PUBLIC_API(JSBranchCallback)
-JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb)
-{
-    JSBranchCallback oldcb;
-
-    if (!cx->branchCallbackWasSet) {
-#ifdef DEBUG
-        if (cx->operationCallback) {
-            fprintf(stderr,
-"JS API usage error: call to JS_SetOperationCallback is followed by\n"
-"invocation of deprecated JS_SetBranchCallback\n");
-            JS_ASSERT(0);
-        }
-#endif
-        cx->branchCallbackWasSet = 1;
-        oldcb = NULL;
-    } else {
-        oldcb = (JSBranchCallback) cx->operationCallback;
-    }
-    if (cb) {
-        cx->operationCount = JSOW_SCRIPT_JUMP;
-        cx->operationLimit = JSOW_SCRIPT_JUMP;
-        cx->operationCallback = (JSOperationCallback) cb;
-    } else {
-        cx->operationCallback = NULL;
-    }
-    return oldcb;
-}
-
-JS_PUBLIC_API(void)
-JS_SetOperationCallbackFunction(JSContext *cx, JSOperationCallback callback)
-{
-    /* Mixed operation and branch callbacks are not supported. */
-    JS_ASSERT(!cx->branchCallbackWasSet);
+JS_PUBLIC_API(JSOperationCallback)
+JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback)
+{
+#ifdef JS_THREADSAFE
+    JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread));
+#endif    
+    JSOperationCallback old = cx->operationCallback;
     cx->operationCallback = callback;
+    return old;
 }
 
 JS_PUBLIC_API(JSOperationCallback)
 JS_GetOperationCallback(JSContext *cx)
 {
-    JS_ASSERT(!cx->branchCallbackWasSet);
     return cx->operationCallback;
 }
 
 JS_PUBLIC_API(void)
 JS_TriggerOperationCallback(JSContext *cx)
 {
-    cx->operationCount = 0;
+    /*
+     * Use JS_ATOMIC_SET in the hope that it will make sure the write
+     * will become immediately visible to other processors polling
+     * cx->operationCallbackFlag. Note that we only care about
+     * visibility here, not read/write ordering.
+     */
+    JS_ATOMIC_SET(&cx->operationCallbackFlag, 1);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_IsRunning(JSContext *cx)
 {
     /* The use of cx->fp below is safe: if we're on trace, it is skipped. */
     VOUCH_DOES_NOT_REQUIRE_STACK();
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -624,25 +624,16 @@ JS_StringToVersion(const char *string);
                                                    option supported for the
                                                    XUL preprocessor and kindred
                                                    beasts. */
 #define JSOPTION_XML            JS_BIT(6)       /* EMCAScript for XML support:
                                                    parse <!-- --> as a token,
                                                    not backward compatible with
                                                    the comment-hiding hack used
                                                    in HTML script tags. */
-#define JSOPTION_NATIVE_BRANCH_CALLBACK \
-                                JS_BIT(7)       /* the branch callback set by
-                                                   JS_SetBranchCallback may be
-                                                   called with a null script
-                                                   parameter, by native code
-                                                   that loops intensively.
-                                                   Deprecated, use
-                                                   JS_SetOperationCallback
-                                                   instead */
 #define JSOPTION_DONT_REPORT_UNCAUGHT \
                                 JS_BIT(8)       /* When returning from the
                                                    outermost API call, prevent
                                                    uncaught exceptions from
                                                    being converted to error
                                                    reports */
 
 #define JSOPTION_RELIMIT        JS_BIT(9)       /* Throw exception on any
@@ -2259,78 +2250,41 @@ extern JS_PUBLIC_API(JSBool)
 JS_CallFunctionName(JSContext *cx, JSObject *obj, const char *name, uintN argc,
                     jsval *argv, jsval *rval);
 
 extern JS_PUBLIC_API(JSBool)
 JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc,
                      jsval *argv, jsval *rval);
 
 /*
- * The maximum value of the operation limit to pass to JS_SetOperationCallback
- * and JS_SetOperationLimit.
+ * These functions allow setting an operation callback that will be called
+ * from the thread the context is associated with some time after any thread
+ * triggered the callback using JS_TriggerOperationCallback(cx).
+ * 
+ * In a threadsafe build the engine internally triggers operation callbacks
+ * under certain circumstances (i.e. GC and title transfer) to force the
+ * context to yield its current request, which the engine always 
+ * automatically does immediately prior to calling the callback function.
+ * The embedding should thus not rely on callbacks being triggered through
+ * the external API only.
+ * 
+ * Important note: Additional callbacks can occur inside the callback handler
+ * if it re-enters the JS engine. The embedding must ensure that the callback
+ * is disconnected before attempting such re-entry.
  */
-#define JS_MAX_OPERATION_LIMIT ((uint32) 0x7FFFFFFF - (uint32) 1)
-
-#define JS_OPERATION_WEIGHT_BASE 4096
-
-extern JS_PUBLIC_API(void)
-JS_SetOperationCallbackFunction(JSContext *cx, JSOperationCallback callback);
+
+extern JS_PUBLIC_API(JSOperationCallback)
+JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback);
 
 extern JS_PUBLIC_API(JSOperationCallback)
 JS_GetOperationCallback(JSContext *cx);
 
-/*
- * Force a call to operation callback at some later moment. The function can be
- * called from an arbitrary thread for any context.
- */
 extern JS_PUBLIC_API(void)
 JS_TriggerOperationCallback(JSContext *cx);
 
-/*
- * Set the limit for the internal operation counter. The engine calls the
- * operation callback When the limit is reached.
- *
- * When operationLimit is JS_OPERATION_WEIGHT_BASE, the callback will be
- * called at least after each backward jump in the interpreter. To minimize
- * the overhead of the callback invocation we suggest at least
- *
- *   100 * JS_OPERATION_WEIGHT_BASE
- *
- * as a value for operationLimit.
- */
-extern JS_PUBLIC_API(void)
-JS_SetOperationLimit(JSContext *cx, uint32 operationLimit);
-
-/*
- * Get the operation limit associated with the operation callback. This API
- * function may be called only when the result of JS_GetOperationCallback(cx)
- * is not null.
- */
-extern JS_PUBLIC_API(uint32)
-JS_GetOperationLimit(JSContext *cx);
-
-extern JS_PUBLIC_API(void)
-JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
-                        uint32 operationLimit);
-
-extern JS_PUBLIC_API(void)
-JS_ClearOperationCallback(JSContext *cx);
-
-/*
- * Note well: JS_SetBranchCallback is deprecated. It is similar to
- *
- *   JS_SetOperationCallback(cx, callback, 4096, NULL);
- *
- * except that the callback will not be called from a long-running native
- * function when JSOPTION_NATIVE_BRANCH_CALLBACK is not set and the top-most
- * frame is native.
- */
-extern JS_PUBLIC_API(JSBranchCallback)
-JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb);
-
 extern JS_PUBLIC_API(JSBool)
 JS_IsRunning(JSContext *cx);
 
 extern JS_PUBLIC_API(JSBool)
 JS_IsConstructing(JSContext *cx);
 
 /*
  * Returns true if a script is executing and its current bytecode is a set
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -582,17 +582,17 @@ array_length_setter(JSContext *cx, JSObj
     if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
         /* Don't reallocate if we're not actually shrinking our slots. */
         jsuint oldsize = ARRAY_DENSE_LENGTH(obj);
         if (oldsize >= newlen && !ResizeSlots(cx, obj, oldsize, newlen))
             return JS_FALSE;
     } else if (oldlen - newlen < (1 << 24)) {
         do {
             --oldlen;
-            if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
+            if (!JS_CHECK_OPERATION_LIMIT(cx) ||
                 !DeleteArrayElement(cx, obj, oldlen)) {
                 return JS_FALSE;
             }
         } while (oldlen != newlen);
     } else {
         /*
          * We are going to remove a lot of indexes in a presumably sparse
          * array. So instead of looping through indexes between newlen and
@@ -603,17 +603,17 @@ array_length_setter(JSContext *cx, JSObj
         iter = JS_NewPropertyIterator(cx, obj);
         if (!iter)
             return JS_FALSE;
 
         /* Protect iter against GC in OBJ_DELETE_PROPERTY. */
         JS_PUSH_TEMP_ROOT_OBJECT(cx, iter, &tvr);
         gap = oldlen - newlen;
         for (;;) {
-            ok = (JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+            ok = (JS_CHECK_OPERATION_LIMIT(cx) &&
                   JS_NextProperty(cx, iter, &id));
             if (!ok)
                 break;
             if (JSVAL_IS_VOID(id))
                 break;
             if (js_IdIsIndex(id, &index) && index - newlen < gap) {
                 ok = OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
                 if (!ok)
@@ -1321,17 +1321,17 @@ array_join_sub(JSContext *cx, JSObject *
         } else {
             sepstr = NULL;      /* indicates to use "," as separator */
             seplen = 1;
         }
     }
 
     /* Use rval to locally root each element value as we loop and convert. */
     for (index = 0; index < length; index++) {
-        ok = (JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+        ok = (JS_CHECK_OPERATION_LIMIT(cx) &&
               GetArrayElement(cx, obj, index, &hole, rval));
         if (!ok)
             goto done;
         if (hole ||
             (op != TO_SOURCE &&
              (JSVAL_IS_VOID(*rval) || JSVAL_IS_NULL(*rval)))) {
             str = cx->runtime->emptyString;
         } else {
@@ -1374,17 +1374,16 @@ array_join_sub(JSContext *cx, JSObject *
             growth > (size_t)-1 / sizeof(jschar)) {
             if (chars) {
                 free(chars);
                 chars = NULL;
             }
             goto done;
         }
         growth *= sizeof(jschar);
-        JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
         if (!chars) {
             chars = (jschar *) malloc(growth);
             if (!chars)
                 goto done;
         } else {
             chars = (jschar *) realloc((ochars = chars), growth);
             if (!chars) {
                 free(ochars);
@@ -1495,17 +1494,17 @@ InitArrayElements(JSContext *cx, JSObjec
         if (end > (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH])
             obj->fslots[JSSLOT_ARRAY_LENGTH] = end;
 
         memcpy(obj->dslots + start, vector, sizeof(jsval) * (end - start));
         return JS_TRUE;
     }
 
     while (start != end) {
-        if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
+        if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !SetArrayElement(cx, obj, start++, *vector++)) {
             return JS_FALSE;
         }
     }
     return JS_TRUE;
 }
 
 static JSBool
@@ -1593,17 +1592,17 @@ array_reverse(JSContext *cx, uintN argc,
     obj = JS_THIS_OBJECT(cx, vp);
     if (!obj || !js_GetLengthProperty(cx, obj, &len))
         return JS_FALSE;
 
     ok = JS_TRUE;
     JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
     half = len / 2;
     for (i = 0; i < half; i++) {
-        ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+        ok = JS_CHECK_OPERATION_LIMIT(cx) &&
              GetArrayElement(cx, obj, i, &hole, &tvr.u.value) &&
              GetArrayElement(cx, obj, len - i - 1, &hole2, vp) &&
              SetOrDeleteArrayElement(cx, obj, len - i - 1, hole, tvr.u.value) &&
              SetOrDeleteArrayElement(cx, obj, i, hole2, *vp);
         if (!ok)
             break;
     }
     JS_POP_TEMP_ROOT(cx, &tvr);
@@ -1778,17 +1777,17 @@ sort_compare(void *arg, const void *a, c
 
     /**
      * array_sort deals with holes and undefs on its own and they should not
      * come here.
      */
     JS_ASSERT(!JSVAL_IS_VOID(av));
     JS_ASSERT(!JSVAL_IS_VOID(bv));
 
-    if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP))
+    if (!JS_CHECK_OPERATION_LIMIT(cx))
         return JS_FALSE;
 
     invokevp = ca->elemroot;
     sp = invokevp;
     *sp++ = ca->fval;
     *sp++ = JSVAL_NULL;
     *sp++ = av;
     *sp++ = bv;
@@ -1816,17 +1815,17 @@ sort_compare(void *arg, const void *a, c
 
 static int
 sort_compare_strings(void *arg, const void *a, const void *b, int *result)
 {
     jsval av = *(const jsval *)a, bv = *(const jsval *)b;
 
     JS_ASSERT(JSVAL_IS_STRING(av));
     JS_ASSERT(JSVAL_IS_STRING(bv));
-    if (!JS_CHECK_OPERATION_LIMIT((JSContext *)arg, JSOW_JUMP))
+    if (!JS_CHECK_OPERATION_LIMIT((JSContext *)arg))
         return JS_FALSE;
 
     *result = (int) js_CompareStrings(JSVAL_TO_STRING(av), JSVAL_TO_STRING(bv));
     return JS_TRUE;
 }
 
 /*
  * The array_sort function below assumes JSVAL_NULL is zero in order to
@@ -1910,17 +1909,17 @@ array_sort(JSContext *cx, uintN argc, js
      * Thus to sort holes and undefs we simply count them, sort the rest
      * of elements, append undefs after them and then make holes after
      * undefs.
      */
     undefs = 0;
     newlen = 0;
     all_strings = JS_TRUE;
     for (i = 0; i < len; i++) {
-        ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
+        ok = JS_CHECK_OPERATION_LIMIT(cx);
         if (!ok)
             goto out;
 
         /* Clear vec[newlen] before including it in the rooted set. */
         vec[newlen] = JSVAL_NULL;
         tvr.count = newlen + 1;
         ok = GetArrayElement(cx, obj, i, &hole, &vec[newlen]);
         if (!ok)
@@ -1996,17 +1995,17 @@ array_sort(JSContext *cx, uintN argc, js
              * Rearrange and string-convert the elements of the vector from
              * the tail here and, after sorting, move the results back
              * starting from the start to prevent overwrite the existing
              * elements.
              */
             i = newlen;
             do {
                 --i;
-                ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
+                ok = JS_CHECK_OPERATION_LIMIT(cx);
                 if (!ok)
                     goto out;
                 v = vec[i];
                 str = js_ValueToString(cx, v);
                 if (!str) {
                     ok = JS_FALSE;
                     goto out;
                 }
@@ -2075,25 +2074,25 @@ array_sort(JSContext *cx, uintN argc, js
     JS_POP_TEMP_ROOT(cx, &tvr);
     JS_free(cx, vec);
     if (!ok)
         return JS_FALSE;
 
     /* Set undefs that sorted after the rest of elements. */
     while (undefs != 0) {
         --undefs;
-        if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
+        if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !SetArrayElement(cx, obj, newlen++, JSVAL_VOID)) {
             return JS_FALSE;
         }
     }
 
     /* Re-create any holes that sorted to the end of the array. */
     while (len > newlen) {
-        if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
+        if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !DeleteArrayElement(cx, obj, --len)) {
             return JS_FALSE;
         }
     }
     *vp = OBJECT_TO_JSVAL(obj);
     return JS_TRUE;
 }
 
@@ -2278,17 +2277,17 @@ array_shift(JSContext *cx, uintN argc, j
         /* Get the to-be-deleted property's value into vp ASAP. */
         if (!GetArrayElement(cx, obj, 0, &hole, vp))
             return JS_FALSE;
 
         /* Slide down the array above the first element. */
         ok = JS_TRUE;
         JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
         for (i = 0; i != length; i++) {
-            ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+            ok = JS_CHECK_OPERATION_LIMIT(cx) &&
                  GetArrayElement(cx, obj, i + 1, &hole, &tvr.u.value) &&
                  SetOrDeleteArrayElement(cx, obj, i, hole, tvr.u.value);
             if (!ok)
                 break;
         }
         JS_POP_TEMP_ROOT(cx, &tvr);
         if (!ok)
             return JS_FALSE;
@@ -2316,17 +2315,17 @@ array_unshift(JSContext *cx, uintN argc,
         /* Slide up the array to make room for argc at the bottom. */
         argv = JS_ARGV(cx, vp);
         if (length > 0) {
             last = length;
             ok = JS_TRUE;
             JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
             do {
                 --last;
-                ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+                ok = JS_CHECK_OPERATION_LIMIT(cx) &&
                      GetArrayElement(cx, obj, last, &hole, &tvr.u.value) &&
                      SetOrDeleteArrayElement(cx, obj, last + argc, hole,
                                              tvr.u.value);
                 if (!ok)
                     break;
             } while (last != 0);
             JS_POP_TEMP_ROOT(cx, &tvr);
             if (!ok)
@@ -2413,17 +2412,17 @@ array_splice(JSContext *cx, uintN argc, 
     }
 
     MUST_FLOW_THROUGH("out");
     JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
 
     /* If there are elements to remove, put them into the return value. */
     if (count > 0) {
         for (last = begin; last < end; last++) {
-            ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+            ok = JS_CHECK_OPERATION_LIMIT(cx) &&
                  GetArrayElement(cx, obj, last, &hole, &tvr.u.value);
             if (!ok)
                 goto out;
 
             /* Copy tvr.u.value to new array unless it's a hole. */
             if (!hole) {
                 ok = SetArrayElement(cx, obj2, last - begin, tvr.u.value);
                 if (!ok)
@@ -2437,28 +2436,28 @@ array_splice(JSContext *cx, uintN argc, 
     }
 
     /* Find the direction (up or down) to copy and make way for argv. */
     if (argc > count) {
         delta = (jsuint)argc - count;
         last = length;
         /* (uint) end could be 0, so can't use vanilla >= test */
         while (last-- > end) {
-            ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+            ok = JS_CHECK_OPERATION_LIMIT(cx) &&
                  GetArrayElement(cx, obj, last, &hole, &tvr.u.value) &&
                  SetOrDeleteArrayElement(cx, obj, last + delta, hole,
                                          tvr.u.value);
             if (!ok)
                 goto out;
         }
         length += delta;
     } else if (argc < count) {
         delta = count - (jsuint)argc;
         for (last = end; last < length; last++) {
-            ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+            ok = JS_CHECK_OPERATION_LIMIT(cx) &&
                  GetArrayElement(cx, obj, last, &hole, &tvr.u.value) &&
                  SetOrDeleteArrayElement(cx, obj, last - delta, hole,
                                          tvr.u.value);
             if (!ok)
                 goto out;
         }
         length -= delta;
     }
@@ -2525,17 +2524,17 @@ array_concat(JSContext *cx, uintN argc, 
         length = 0;
     }
 
     MUST_FLOW_THROUGH("out");
     JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
 
     /* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
     for (i = 0; i <= argc; i++) {
-        ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
+        ok = JS_CHECK_OPERATION_LIMIT(cx);
         if (!ok)
             goto out;
         v = argv[i];
         if (!JSVAL_IS_PRIMITIVE(v)) {
             JSObject *wobj;
 
             aobj = JSVAL_TO_OBJECT(v);
             wobj = js_GetWrappedObject(cx, aobj);
@@ -2546,17 +2545,17 @@ array_concat(JSContext *cx, uintN argc, 
                                       &tvr.u.value);
                 if (!ok)
                     goto out;
                 alength = ValueIsLength(cx, &tvr.u.value);
                 ok = !JSVAL_IS_NULL(tvr.u.value);
                 if (!ok)
                     goto out;
                 for (slot = 0; slot < alength; slot++) {
-                    ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+                    ok = JS_CHECK_OPERATION_LIMIT(cx) &&
                          GetArrayElement(cx, aobj, slot, &hole,
                                          &tvr.u.value);
                     if (!ok)
                         goto out;
 
                     /*
                      * Per ECMA 262, 15.4.4.4, step 9, ignore non-existent
                      * properties.
@@ -2652,17 +2651,17 @@ array_slice(JSContext *cx, uintN argc, j
     if (!nobj)
         return JS_FALSE;
     *vp = OBJECT_TO_JSVAL(nobj);
 
     MUST_FLOW_THROUGH("out");
     JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
 
     for (slot = begin; slot < end; slot++) {
-        ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+        ok = JS_CHECK_OPERATION_LIMIT(cx) &&
              GetArrayElement(cx, obj, slot, &hole, &tvr.u.value);
         if (!ok)
             goto out;
         if (!hole) {
             ok = SetArrayElement(cx, nobj, slot - begin, tvr.u.value);
             if (!ok)
                 goto out;
         }
@@ -2724,17 +2723,17 @@ array_indexOfHelper(JSContext *cx, JSBoo
         stop = 0;
         direction = -1;
     } else {
         stop = length - 1;
         direction = 1;
     }
 
     for (;;) {
-        if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
+        if (!JS_CHECK_OPERATION_LIMIT(cx) ||
             !GetArrayElement(cx, obj, (jsuint)i, &hole, vp)) {
             return JS_FALSE;
         }
         if (!hole && js_StrictlyEqual(cx, *vp, tosearch))
             return js_NewNumberInRootedValue(cx, i, vp);
         if (i == stop)
             goto not_found;
         i += direction;
@@ -2873,17 +2872,17 @@ array_extra(JSContext *cx, ArrayExtraMod
     if (!elemroot)
         return JS_FALSE;
 
     MUST_FLOW_THROUGH("out");
     ok = JS_TRUE;
     invokevp = elemroot + 1;
 
     for (i = start; i != end; i += step) {
-        ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
+        ok = JS_CHECK_OPERATION_LIMIT(cx) &&
              GetArrayElement(cx, obj, i, &hole, elemroot);
         if (!ok)
             goto out;
         if (hole)
             continue;
 
         /*
          * Push callable and 'this', then args. We must do this for every
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -75,19 +75,16 @@
 /*
  * The index for JSThread info, returned by PR_NewThreadPrivateIndex.  The
  * index value is visible and shared by all threads, but the data associated
  * with it is private to each thread.
  */
 static PRUintn threadTPIndex;
 static JSBool  tpIndexInited = JS_FALSE;
 
-static void
-InitOperationLimit(JSContext *cx);
-
 JS_BEGIN_EXTERN_C
 JSBool
 js_InitThreadPrivateIndex(void (*ptr)(void *))
 {
     PRStatus status;
 
     if (tpIndexInited)
         return JS_TRUE;
@@ -276,17 +273,16 @@ js_NewContext(JSRuntime *rt, size_t stac
      * runtime list. After that it can be accessed from another thread via
      * js_ContextIterator.
      */
     cx = (JSContext *) calloc(1, sizeof *cx);
     if (!cx)
         return NULL;
 
     cx->runtime = rt;
-    js_InitOperationLimit(cx);
     cx->debugHooks = &rt->globalDebugHooks;
 #if JS_STACK_GROWTH_DIRECTION > 0
     cx->stackLimit = (jsuword) -1;
 #endif
     cx->scriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
 #ifdef JS_THREADSAFE
     cx->gcLocalFreeLists = (JSGCFreeListSet *) &js_GCEmptyFreeListSet;
     js_InitContextThread(cx, thread);
@@ -618,16 +614,31 @@ js_ContextIterator(JSRuntime *rt, JSBool
     if (&cx->link == &rt->contextList)
         cx = NULL;
     *iterp = cx;
     if (unlocked)
         JS_UNLOCK_GC(rt);
     return cx;
 }
 
+JS_FRIEND_API(JSContext *)
+js_NextActiveContext(JSRuntime *rt, JSContext *cx)
+{
+    JSContext *iter = cx;
+#ifdef JS_THREADSAFE
+    while ((cx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) {
+        if (cx->requestDepth)
+            break;
+    }
+    return cx;
+#else
+    return js_ContextIterator(rt, JS_FALSE, &iter);
+#endif           
+}
+
 static JSDHashNumber
 resolving_HashKey(JSDHashTable *table, const void *ptr)
 {
     const JSResolvingKey *key = (const JSResolvingKey *)ptr;
 
     return ((JSDHashNumber)JS_PTR_TO_UINT32(key->obj) >> JSVAL_TAGBITS) ^ key->id;
 }
 
@@ -1441,41 +1452,47 @@ JS_FRIEND_API(const JSErrorFormatString 
 js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber)
 {
     if ((errorNumber > 0) && (errorNumber < JSErr_Limit))
         return &js_ErrorFormatString[errorNumber];
     return NULL;
 }
 
 JSBool
-js_ResetOperationCount(JSContext *cx)
+js_InvokeOperationCallback(JSContext *cx)
 {
-    JSScript *script;
-    JSStackFrame *fp;
+    JS_ASSERT(cx->operationCallbackFlag);
+    
+    /*
+     * Reset the callback flag first, then yield. If another thread is racing
+     * us here we will accumulate another callback request which will be 
+     * serviced at the next opportunity.
+     */
+    cx->operationCallbackFlag = 0;
 
-    JS_ASSERT(cx->operationCount <= 0);
-    JS_ASSERT(cx->operationLimit > 0);
+    /*
+     * We automatically yield the current context every time the operation
+     * callback is hit since we might be called as a result of an impending
+     * GC, which would deadlock if we do not yield. Operation callbacks
+     * are supposed to happen rarely (seconds, not milliseconds) so it is
+     * acceptable to yield at every callback.
+     */
+#ifdef JS_THREADSAFE    
+    JS_YieldRequest(cx);
+#endif
 
-    cx->operationCount = (int32) cx->operationLimit;
     JSOperationCallback cb = cx->operationCallback;
-    if (cb) {
-        if (!cx->branchCallbackWasSet)
-            return cb(cx);
 
-        /*
-         * Invoke the deprecated branch callback. It may be called only when
-         * the top-most frame is scripted or JSOPTION_NATIVE_BRANCH_CALLBACK
-         * is set.
-         */
-        fp = js_GetTopStackFrame(cx);
-        script = fp ? fp->script : NULL;
-        if (script || JS_HAS_OPTION(cx, JSOPTION_NATIVE_BRANCH_CALLBACK))
-            return ((JSBranchCallback) cb)(cx, script);
-    }
-    return JS_TRUE;
+    /*
+     * Important: Additional callbacks can occur inside the callback handler
+     * if it re-enters the JS engine. The embedding must ensure that the
+     * callback is disconnected before attempting such re-entry.
+     */
+
+    return !cb || cb(cx);
 }
 
 #ifndef JS_TRACER
 /* This is defined in jstracer.cpp in JS_TRACER builds. */
 extern JS_FORCES_STACK JSStackFrame *
 js_GetTopStackFrame(JSContext *cx)
 {
     return cx->fp;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -832,20 +832,20 @@ JS_STATIC_ASSERT(sizeof(JSTempValueUnion
 #define JS_PUSH_TEMP_ROOT_SCRIPT(cx,script_,tvr)                              \
     JS_PUSH_TEMP_ROOT_COMMON(cx, script_, tvr, JSTVU_SCRIPT, script)
 
 
 #define JSRESOLVE_INFER         0xffff  /* infer bits from current bytecode */
 
 struct JSContext {
     /*
-     * Operation count. It is declared as the first field in the struct to
-     * ensure the fastest possible access.
+     * If this flag is set, we were asked to call back the operation callback
+     * as soon as possible.
      */
-    volatile int32      operationCount;
+    volatile jsint      operationCallbackFlag;
 
     /* JSRuntime contextList linkage. */
     JSCList             link;
 
 #if JS_HAS_XML_SUPPORT
     /*
      * Bit-set formed from binary exponentials of the XML_* tiny-ids defined
      * for boolean settings in jsxml.c, plus an XSF_CACHE_VALID bit.  Together
@@ -945,22 +945,17 @@ struct JSContext {
     char                *lastMessage;
 #ifdef DEBUG
     void                *tracefp;
 #endif
 
     /* Per-context optional error reporter. */
     JSErrorReporter     errorReporter;
 
-    /*
-     * Flag indicating that operationCallback stores the deprecated branch
-     * callback.
-     */
-    uint32              branchCallbackWasSet   :    1;
-    uint32              operationLimit         :    31;
+    /* Branch callback. */
     JSOperationCallback operationCallback;
 
     /* Interpreter activation count. */
     uintN               interpLevel;
 
     /* Client opaque pointers. */
     void                *data;
     void                *data2;
@@ -1206,16 +1201,24 @@ js_ContextFromLinkField(JSCList *link)
 /*
  * If unlocked, acquire and release rt->gcLock around *iterp update; otherwise
  * the caller must be holding rt->gcLock.
  */
 extern JSContext *
 js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp);
 
 /*
+ * Iterate through contexts with active requests. The caller must be holding
+ * rt->gcLock in case of a thread-safe build, or otherwise guarantee that the
+ * context list is not alternated asynchroniously.
+ */
+extern JS_FRIEND_API(JSContext *)
+js_NextActiveContext(JSRuntime *, JSContext *);
+
+/*
  * JSClass.resolve and watchpoint recursion damping machinery.
  */
 extern JSBool
 js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
                   JSResolvingEntry **entryp);
 
 extern void
 js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
@@ -1356,82 +1359,29 @@ extern JSErrorFormatString js_ErrorForma
  */
 #if JS_STACK_GROWTH_DIRECTION > 0
 # define JS_CHECK_STACK_SIZE(cx, lval)  ((jsuword)&(lval) < (cx)->stackLimit)
 #else
 # define JS_CHECK_STACK_SIZE(cx, lval)  ((jsuword)&(lval) > (cx)->stackLimit)
 #endif
 
 /*
- * Update the operation counter according to the given weight and call the
- * operation callback when we reach the operation limit. To make this
- * frequently executed macro faster we decrease the counter from
- * JSContext.operationLimit and compare against zero to check the limit.
- *
+ * If the operation callback flag was set, call the operation callback.
  * This macro can run the full GC. Return true if it is OK to continue and
  * false otherwise.
  */
-#define JS_CHECK_OPERATION_LIMIT(cx, weight)                                  \
-    (JS_CHECK_OPERATION_WEIGHT(weight),                                       \
-     (((cx)->operationCount -= (weight)) > 0 || js_ResetOperationCount(cx)))
-
-/*
- * A version of JS_CHECK_OPERATION_LIMIT that just updates the operation count
- * without calling the operation callback or any other API. This macro resets
- * the count to 0 when it becomes negative to prevent a wrap-around when the
- * macro is called repeatably.
- */
-#define JS_COUNT_OPERATION(cx, weight)                                        \
-    ((void)(JS_CHECK_OPERATION_WEIGHT(weight),                                \
-            (cx)->operationCount = ((cx)->operationCount > 0)                 \
-                                   ? (cx)->operationCount - (weight)          \
-                                   : 0))
+#define JS_CHECK_OPERATION_LIMIT(cx) \
+    (!(cx)->operationCallbackFlag || js_InvokeOperationCallback(cx))
 
 /*
- * The implementation of the above macros assumes that subtracting weights
- * twice from a positive number does not wrap-around INT32_MIN.
- */
-#define JS_CHECK_OPERATION_WEIGHT(weight)                                     \
-    (JS_ASSERT((uint32) (weight) > 0),                                        \
-     JS_ASSERT((uint32) (weight) < JS_BIT(30)))
-
-/* Relative operations weights. */
-#define JSOW_JUMP                   1
-#define JSOW_ALLOCATION             100
-#define JSOW_LOOKUP_PROPERTY        5
-#define JSOW_GET_PROPERTY           10
-#define JSOW_SET_PROPERTY           20
-#define JSOW_NEW_PROPERTY           200
-#define JSOW_DELETE_PROPERTY        30
-#define JSOW_ENTER_SHARP            JS_OPERATION_WEIGHT_BASE
-#define JSOW_SCRIPT_JUMP            JS_OPERATION_WEIGHT_BASE
-
-/*
- * Reset the operation count and call the operation callback assuming that the
- * operation limit is reached.
+ * Invoke the operation callback and return false if the current execution
+ * is to be terminated.
  */
 extern JSBool
-js_ResetOperationCount(JSContext *cx);
-
-static JS_INLINE void
-js_InitOperationLimit(JSContext *cx)
-{
-    /*
-     * Set the limit to 1 + max to detect if JS_SetOperationLimit() was ever
-     * called.
-     */
-    cx->operationCount = (int32) JS_MAX_OPERATION_LIMIT + 1;
-    cx->operationLimit = JS_MAX_OPERATION_LIMIT + 1;
-}
-
-static JS_INLINE JSBool
-js_HasOperationLimit(JSContext *cx)
-{
-    return cx->operationLimit <= JS_MAX_OPERATION_LIMIT;
-}
+js_InvokeOperationCallback(JSContext *cx);
 
 /*
  * Get the current cx->fp, first lazily instantiating stack frames if needed.
  * (Do not access cx->fp directly except in JS_REQUIRES_STACK code.)
  *
  * Defined in jstracer.cpp if JS_TRACER is defined.
  */
 extern JS_FORCES_STACK JSStackFrame *
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -2473,8 +2473,26 @@ js_DateSetSeconds(JSContext *cx, JSObjec
 JS_FRIEND_API(jsdouble)
 js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj)
 {
     jsdouble utctime;
     if (!GetUTCTime(cx, obj, NULL, &utctime))
         return 0;
     return utctime;
 }
+
+#ifdef JS_THREADSAFE
+#include "prinrval.h"
+
+uint32 
+js_IntervalNow() 
+{
+    return uint32(PR_IntervalToMilliseconds(PR_IntervalNow()));
+}
+
+#else /* !JS_THREADSAFE */
+
+uint32 
+js_IntervalNow() 
+{
+    return uint32(PRMJ_Now() / PRMJ_USEC_PER_MSEC);
+}
+#endif
--- a/js/src/jsdate.h
+++ b/js/src/jsdate.h
@@ -114,11 +114,16 @@ extern JS_FRIEND_API(void)
 js_DateSetMinutes(JSContext *cx, JSObject *obj, int minutes);
 
 extern JS_FRIEND_API(void)
 js_DateSetSeconds(JSContext *cx, JSObject *obj, int seconds);
 
 extern JS_FRIEND_API(jsdouble)
 js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj);
 
+typedef uint32 JSIntervalTime;
+
+JSIntervalTime
+js_IntervalNow();
+
 JS_END_EXTERN_C
 
 #endif /* jsdate_h___ */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2020,17 +2020,16 @@ testReservedObjects:
 
     /* This is not thread-safe for thread-local allocations. */
     METER_IF(flags & GCF_LOCK, rt->gcStats.lockborn++);
 
 #ifdef JS_THREADSAFE
     if (gcLocked)
         JS_UNLOCK_GC(rt);
 #endif
-    JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
     return thing;
 
 fail:
 #ifdef JS_THREADSAFE
     if (gcLocked)
         JS_UNLOCK_GC(rt);
 #endif
     METER(astats->fail++);
@@ -2168,17 +2167,16 @@ RefillDoubleFreeList(JSContext *cx)
                 JS_ASSERT(index + bit < DOUBLES_PER_ARENA);
                 JS_ASSERT_IF(index + bit == DOUBLES_PER_ARENA - 1, !list);
                 cell->link = list;
                 list = cell;
             }
         } while (bit != 0);
     }
     JS_ASSERT(list);
-    JS_COUNT_OPERATION(cx, JSOW_ALLOCATION * JS_BITS_PER_WORD);
 
     /*
      * We delegate assigning cx->doubleFreeList to js_NewDoubleInRootedValue as
      * it immediately consumes the head of the list.
      */
     return list;
 }
 
@@ -3307,17 +3305,17 @@ js_GC(JSContext *cx, JSGCInvocationKind 
     if (!(gckind & GC_LOCK_HELD))
         JS_LOCK_GC(rt);
 
     METER(rt->gcStats.poke++);
     rt->gcPoke = JS_FALSE;
 
 #ifdef JS_THREADSAFE
     JS_ASSERT(cx->thread->id == js_CurrentThreadId());
-
+    
     /* Bump gcLevel and return rather than nest on this thread. */
     if (rt->gcThread == cx->thread) {
         JS_ASSERT(rt->gcLevel > 0);
         rt->gcLevel++;
         METER_UPDATE_MAX(rt->gcStats.maxlevel, rt->gcLevel);
         if (!(gckind & GC_LOCK_HELD))
             JS_UNLOCK_GC(rt);
         return;
@@ -3378,16 +3376,24 @@ js_GC(JSContext *cx, JSGCInvocationKind 
             JS_UNLOCK_GC(rt);
         return;
     }
 
     /* No other thread is in GC, so indicate that we're now in GC. */
     rt->gcLevel = 1;
     rt->gcThread = cx->thread;
 
+    /*
+     * Notify all operation callbacks, which will give them a chance to
+     * yield their current request. Contexts that are not currently
+     * executing will perform their callback at some later point,
+     * which then will be unnecessary, but harmless.
+     */
+    js_NudgeOtherContexts(cx);
+
     /* Wait for all other requests to finish. */
     while (rt->requestCount > 0)
         JS_AWAIT_REQUEST_DONE(rt);
 
 #else  /* !JS_THREADSAFE */
 
     /* Bump gcLevel and return rather than nest; the outer gc will restart. */
     rt->gcLevel++;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -49,16 +49,17 @@
 #include "jsarena.h" /* Added by JSIFY */
 #include "jsutil.h" /* Added by JSIFY */
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jscntxt.h"
+#include "jsdate.h"
 #include "jsversion.h"
 #include "jsdbgapi.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsinterp.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsnum.h"
@@ -2679,17 +2680,17 @@ js_Interpret(JSContext *cx)
 #endif /* !JS_TRACER */
 
     /*
      * Prepare to call a user-supplied branch handler, and abort the script
      * if it returns false.
      */
 #define CHECK_BRANCH()                                                        \
     JS_BEGIN_MACRO                                                            \
-        if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_SCRIPT_JUMP))                  \
+        if (!JS_CHECK_OPERATION_LIMIT(cx))                                    \
             goto error;                                                       \
     JS_END_MACRO
 
 #define BRANCH(n)                                                             \
     JS_BEGIN_MACRO                                                            \
         regs.pc += n;                                                         \
         if (n <= 0) {                                                         \
             CHECK_BRANCH();                                                   \
--- a/js/src/jslock.cpp
+++ b/js/src/jslock.cpp
@@ -454,16 +454,48 @@ js_FinishSharingTitle(JSContext *cx, JST
         }
     }
 
     title->ownercx = NULL;  /* NB: set last, after lock init */
     JS_RUNTIME_METER(cx->runtime, sharedTitles);
 }
 
 /*
+ * Notify all contexts that are currently in a request, which will give them a
+ * chance to yield their current request.
+ */
+void
+js_NudgeOtherContexts(JSContext *cx)
+{
+    JSRuntime *rt = cx->runtime;
+    JSContext *acx = NULL;
+
+    while ((acx = js_NextActiveContext(rt, acx)) != NULL) {
+        if (cx != acx)
+            JS_TriggerOperationCallback(acx);
+    }
+}
+
+/*
+ * Notify all contexts that are currently in a request and execute on this
+ * specific thread.
+ */
+void
+js_NudgeThread(JSContext *cx, JSThread *thread)
+{
+    JSRuntime *rt = cx->runtime;
+    JSContext *acx = NULL;
+    
+    while ((acx = js_NextActiveContext(rt, acx)) != NULL) {
+        if (cx != acx && cx->thread == thread)
+            JS_TriggerOperationCallback(acx);
+    }
+}
+
+/*
  * Given a title with apparently non-null ownercx different from cx, try to
  * set ownercx to cx, claiming exclusive (single-threaded) ownership of title.
  * If we claim ownership, return true.  Otherwise, we wait for ownercx to be
  * set to null (indicating that title is multi-threaded); or if waiting would
  * deadlock, we set ownercx to null ourselves via ShareTitle.  In any case,
  * once ownercx is null we return false.
  */
 static JSBool
@@ -556,16 +588,18 @@ ClaimTitle(JSTitle *title, JSContext *cx
             if (rt->gcThread != cx->thread) {
                 JS_ASSERT(rt->requestCount > 0);
                 rt->requestCount--;
                 if (rt->requestCount == 0)
                     JS_NOTIFY_REQUEST_DONE(rt);
             }
         }
 
+        js_NudgeThread(cx, ownercx->thread);
+
         /*
          * We know that some other thread's context owns title, which is now
          * linked onto rt->titleSharingTodo, awaiting the end of that other
          * thread's request.  So it is safe to wait on rt->titleSharingDone.
          */
         cx->titleToShare = title;
         stat = PR_WaitCondVar(rt->titleSharingDone, PR_INTERVAL_NO_TIMEOUT);
         JS_ASSERT(stat != PR_FAILURE);
--- a/js/src/jslock.h
+++ b/js/src/jslock.h
@@ -114,16 +114,17 @@ struct JSTitle {
 
 /*
  * Atomic increment and decrement for a reference counter, given jsrefcount *p.
  * NB: jsrefcount is int32, aka PRInt32, so that pratom.h functions work.
  */
 #define JS_ATOMIC_INCREMENT(p)      PR_AtomicIncrement((PRInt32 *)(p))
 #define JS_ATOMIC_DECREMENT(p)      PR_AtomicDecrement((PRInt32 *)(p))
 #define JS_ATOMIC_ADD(p,v)          PR_AtomicAdd((PRInt32 *)(p), (PRInt32)(v))
+#define JS_ATOMIC_SET(p,v)          PR_AtomicSet((PRInt32 *)(p), (PRInt32)(v))
 
 #define js_CurrentThreadId()        (jsword)PR_GetCurrentThread()
 #define JS_NEW_LOCK()               PR_NewLock()
 #define JS_DESTROY_LOCK(l)          PR_DestroyLock(l)
 #define JS_ACQUIRE_LOCK(l)          PR_Lock(l)
 #define JS_RELEASE_LOCK(l)          PR_Unlock(l)
 
 #define JS_NEW_CONDVAR(l)           PR_NewCondVar(l)
@@ -193,16 +194,19 @@ extern void js_CleanupLocks();
 extern void js_TransferTitle(JSContext *, JSTitle *, JSTitle *);
 extern JS_FRIEND_API(jsval)
 js_GetSlotThreadSafe(JSContext *, JSObject *, uint32);
 extern void js_SetSlotThreadSafe(JSContext *, JSObject *, uint32, jsval);
 extern void js_InitLock(JSThinLock *);
 extern void js_FinishLock(JSThinLock *);
 extern void js_FinishSharingTitle(JSContext *cx, JSTitle *title);
 
+extern void js_NudgeOtherContexts(JSContext *cx);
+extern void js_NudgeThread(JSContext *cx, JSThread *thread);
+
 #ifdef DEBUG
 
 #define JS_IS_RUNTIME_LOCKED(rt)        js_IsRuntimeLocked(rt)
 #define JS_IS_OBJ_LOCKED(cx,obj)        js_IsObjLocked(cx,obj)
 #define JS_IS_TITLE_LOCKED(cx,title)    js_IsTitleLocked(cx,title)
 
 extern JSBool js_IsRuntimeLocked(JSRuntime *rt);
 extern JSBool js_IsObjLocked(JSContext *cx, JSObject *obj);
@@ -232,16 +236,17 @@ extern void js_SetScopeInfo(JSScope *sco
         JS_LOCK_RUNTIME_VOID(_rt, e);                                         \
     JS_END_MACRO
 
 #else  /* !JS_THREADSAFE */
 
 #define JS_ATOMIC_INCREMENT(p)      (++*(p))
 #define JS_ATOMIC_DECREMENT(p)      (--*(p))
 #define JS_ATOMIC_ADD(p,v)          (*(p) += (v))
+#define JS_ATOMIC_SET(p,v)          (*(p) = (v))
 
 #define JS_CurrentThreadId() 0
 #define JS_NEW_LOCK()               NULL
 #define JS_DESTROY_LOCK(l)          ((void)0)
 #define JS_ACQUIRE_LOCK(l)          ((void)0)
 #define JS_RELEASE_LOCK(l)          ((void)0)
 #define JS_LOCK(cx, tl)             ((void)0)
 #define JS_UNLOCK(cx, tl)           ((void)0)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -458,17 +458,17 @@ js_EnterSharpObject(JSContext *cx, JSObj
     JSHashTable *table;
     JSIdArray *ida;
     JSHashNumber hash;
     JSHashEntry *he, **hep;
     jsatomid sharpid;
     char buf[20];
     size_t len;
 
-    if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_ENTER_SHARP))
+    if (!JS_CHECK_OPERATION_LIMIT(cx))
         return NULL;
 
     /* Set to null in case we return an early error. */
     *sp = NULL;
     map = &cx->sharpObjectMap;
     table = map->table;
     if (!table) {
         table = JS_NewHashTable(8, js_hash_object, JS_CompareValues,
@@ -3569,17 +3569,16 @@ js_LookupPropertyWithFlags(JSContext *cx
     JSResolvingKey key;
     JSResolvingEntry *entry;
     uint32 generation;
     JSNewResolveOp newresolve;
     JSBool ok;
 
     /* Convert string indices to integers if appropriate. */
     CHECK_FOR_STRING_INDEX(id);
-    JS_COUNT_OPERATION(cx, JSOW_LOOKUP_PROPERTY);
 
     /* Search scopes starting with obj and following the prototype link. */
     start = obj;
     for (protoIndex = 0; ; protoIndex++) {
         JS_LOCK_OBJ(cx, obj);
         scope = OBJ_SCOPE(obj);
         if (scope->object == obj) {
             sprop = SCOPE_GET_PROPERTY(scope, id);
@@ -3958,17 +3957,16 @@ js_GetPropertyHelper(JSContext *cx, JSOb
     int protoIndex;
     JSObject *obj2;
     JSProperty *prop;
     JSScopeProperty *sprop;
 
     JS_ASSERT_IF(entryp, !JS_ON_TRACE(cx));
     /* Convert string indices to integers if appropriate. */
     CHECK_FOR_STRING_INDEX(id);
-    JS_COUNT_OPERATION(cx, JSOW_GET_PROPERTY);
 
     shape = OBJ_SHAPE(obj);
     protoIndex = js_LookupPropertyWithFlags(cx, obj, id, cx->resolveFlags,
                                             &obj2, &prop);
     if (protoIndex < 0)
         return JS_FALSE;
     if (!prop) {
         *vp = JSVAL_VOID;
@@ -4059,17 +4057,16 @@ js_SetPropertyHelper(JSContext *cx, JSOb
     JSScope *scope;
     uintN attrs, flags;
     intN shortid;
     JSClass *clasp;
     JSPropertyOp getter, setter;
 
     /* Convert string indices to integers if appropriate. */
     CHECK_FOR_STRING_INDEX(id);
-    JS_COUNT_OPERATION(cx, JSOW_SET_PROPERTY);
 
     /*
      * We peek at OBJ_SCOPE(obj) without locking obj. Any race means a failure
      * to seal before sharing, which is inherently ambiguous.
      */
     if (SCOPE_IS_SEALED(OBJ_SCOPE(obj)) && OBJ_SCOPE(obj)->object == obj) {
         flags = JSREPORT_ERROR;
         goto read_only_error;
@@ -4324,17 +4321,16 @@ js_DeleteProperty(JSContext *cx, JSObjec
     JSScopeProperty *sprop;
     JSScope *scope;
     JSBool ok;
 
     *rval = JSVAL_TRUE;
 
     /* Convert string indices to integers if appropriate. */
     CHECK_FOR_STRING_INDEX(id);
-    JS_COUNT_OPERATION(cx, JSOW_DELETE_PROPERTY);
 
     if (!js_LookupProperty(cx, obj, id, &proto, &prop))
         return JS_FALSE;
     if (!prop || proto != obj) {
         /*
          * If the property was found in a native prototype, check whether it's
          * shared and permanent.  Such a property stands for direct properties
          * in all delegating objects, matching ECMA semantics without bloating
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -608,16 +608,21 @@ typedef enum JSContextOp {
  *                      callback may perform its own cleanup and must always
  *                      return true.
  *   Any other value    For future compatibility the callback must do nothing
  *                      and return true in this case.
  */
 typedef JSBool
 (* JSContextCallback)(JSContext *cx, uintN contextOp);
 
+#ifndef JS_THREADSAFE
+typedef void
+(* JSHeartbeatCallback)(JSRuntime *rt);
+#endif
+
 typedef enum JSGCStatus {
     JSGC_BEGIN,
     JSGC_END,
     JSGC_MARK_END,
     JSGC_FINALIZE_END
 } JSGCStatus;
 
 typedef JSBool
--- a/js/src/jsregexp.cpp
+++ b/js/src/jsregexp.cpp
@@ -2627,21 +2627,19 @@ PushBackTrackState(REGlobalData *gData, 
 
     ptrdiff_t btsize = gData->backTrackStackSize;
     ptrdiff_t btincr = ((char *)result + sz) -
                        ((char *)gData->backTrackStack + btsize);
 
     re_debug("\tBT_Push: %lu,%lu",
              (unsigned long) parenIndex, (unsigned long) parenCount);
 
-    JS_COUNT_OPERATION(gData->cx, JSOW_JUMP * (1 + parenCount));
     if (btincr > 0) {
         ptrdiff_t offset = (char *)result - (char *)gData->backTrackStack;
 
-        JS_COUNT_OPERATION(gData->cx, JSOW_ALLOCATION);
         btincr = JS_ROUNDUP(btincr, btsize);
         JS_ARENA_GROW_CAST(gData->backTrackStack, REBackTrackData *,
                            &gData->cx->regexpPool, btsize, btincr);
         if (!gData->backTrackStack) {
             js_ReportOutOfScriptQuota(gData->cx);
             gData->ok = JS_FALSE;
             return NULL;
         }
@@ -3808,17 +3806,17 @@ ExecuteREBytecode(REGlobalData *gData, R
 
         /*
          *  If the match failed and there's a backtrack option, take it.
          *  Otherwise this is a complete and utter failure.
          */
         if (!result) {
             if (gData->cursz == 0)
                 return NULL;
-            if (!JS_CHECK_OPERATION_LIMIT(gData->cx, JSOW_JUMP)) {
+            if (!JS_CHECK_OPERATION_LIMIT(gData->cx)) {
                 gData->ok = JS_FALSE;
                 return NULL;
             }
 
             /* Potentially detect explosive regex here. */
             gData->backTrackCount++;
             if (gData->backTrackLimit &&
                 gData->backTrackCount >= gData->backTrackLimit) {
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -1022,18 +1022,16 @@ js_AddScopeProperty(JSContext *cx, JSSco
 
     /*
      * Search for id in order to claim its entry, allocating a property tree
      * node if one doesn't already exist for our parameters.
      */
     spp = js_SearchScope(scope, id, JS_TRUE);
     sprop = overwriting = SPROP_FETCH(spp);
     if (!sprop) {
-        JS_COUNT_OPERATION(cx, JSOW_NEW_PROPERTY);
-
         /* Check whether we need to grow, if the load factor is >= .75. */
         size = SCOPE_CAPACITY(scope);
         if (scope->entryCount + scope->removedCount >= size - (size >> 2)) {
             if (scope->removedCount >= size >> 2) {
                 METER(compresses);
                 change = 0;
             } else {
                 METER(grows);
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -1248,26 +1248,22 @@ TraceRecorder::TraceRecorder(JSContext* 
                                               *(ti->globalSlots),
                                               ti->nStackTypes);
     }
 
     /* read into registers all values on the stack and all globals we know so far */
     import(treeInfo, lirbuf->sp, stackSlots, ngslots, callDepth, typeMap);
 
     if (fragment == fragment->root) {
-        LIns* counter = lir->insLoadi(cx_ins,
-                                      offsetof(JSContext, operationCount));
-        if (js_HasOperationLimit(cx)) {
-            /* Add code to decrease the operationCount if the embedding relies
-               on its auto-updating. */
-            counter = lir->ins2i(LIR_sub, counter, JSOW_SCRIPT_JUMP);
-            lir->insStorei(counter, cx_ins,
-                           offsetof(JSContext, operationCount));
-        }
-        guard(false, lir->ins2i(LIR_le, counter, 0), snapshot(TIMEOUT_EXIT));
+        /*
+         * We poll the operation callback request flag. It is updated asynchronously whenever 
+         * the callback is to be invoked.
+         */
+        LIns* x = lir->insLoadi(cx_ins, offsetof(JSContext, operationCallbackFlag));
+        guard(true, lir->ins_eq0(x), snapshot(TIMEOUT_EXIT));
     }
 
     /* If we are attached to a tree call guard, make sure the guard the inner tree exited from
        is what we expect it to be. */
     if (_anchor && _anchor->exitType == NESTED_EXIT) {
         LIns* nested_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state,
                                                 offsetof(InterpState, lastTreeExitGuard)),
                                                 "lastTreeExitGuard");
@@ -4220,16 +4216,20 @@ js_MonitorLoopEdge(JSContext* cx, uintN&
     /* Make sure the shape of the global object still matches (this might flush the JIT cache). */
     JSObject* globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain);
     uint32 globalShape = -1;
     SlotList* globalSlots = NULL;
 
     if (!js_CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots))
         js_FlushJITCache(cx);
 
+    /* Do not enter the JIT code with a pending operation callback. */
+    if (cx->operationCallbackFlag)
+        return false;
+    
     jsbytecode* pc = cx->fp->regs->pc;
 
     if (oracle.getHits(pc) >= 0 &&
         oracle.getHits(pc)+1 < HOTLOOP) {
         oracle.hit(pc);
         return false;
     }
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -51,27 +51,30 @@
 #include "jsarena.h"
 #include "jsutil.h"
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbuiltins.h"
 #include "jscntxt.h"
+#include "jsdate.h"
 #include "jsdbgapi.h"
 #include "jsemit.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsparse.h"
 #include "jsscope.h"
 #include "jsscript.h"
 
+#include "prmjtime.h"
+
 #ifdef LIVECONNECT
 #include "jsjava.h"
 #endif
 
 #ifdef JSDEBUGGER
 #include "jsdebug.h"
 #ifdef JSDEBUGGER_JAVA_UI
 #include "jsdjava.h"
@@ -100,26 +103,62 @@ typedef enum JSShellExitCode {
 size_t gStackChunkSize = 8192;
 
 /* Assume that we can not use more than 5e5 bytes of C stack by default. */
 static size_t gMaxStackSize = 500000;
 static jsuword gStackBase;
 
 static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
 
-static jsdouble gOperationTimeout = -1.0;
+static uint32 gOperationLimit = 0;
+
+static JSBool
+SetTimeoutValue(JSContext *cx, jsdouble t);
+
+static double
+GetTimeoutValue(JSContext *cx);
+
+static void
+StopWatchdog(JSRuntime *rt);
+
+static JSBool
+StartWatchdog(JSRuntime *rt);
 
 /*
  * Watchdog thread state.
  */
 #ifdef JS_THREADSAFE
-static PRCondVar *gWatchdogWakeup;
-static PRThread *gWatchdogThread;
-static PRIntervalTime gWatchdogSleepDuration = 0;
-static PRIntervalTime gLastWatchdogWakeup;
+static PRCondVar *gWatchdogWakeup = NULL;
+static PRThread *gWatchdogThread = NULL;
+
+/*
+ * Holding the gcLock already guarantees that the context list is locked when
+ * the watchdog thread walks it.
+ */
+
+#define WITH_LOCKED_CONTEXT_LIST(x)             \
+    JS_BEGIN_MACRO                              \
+        x;                                      \
+    JS_END_MACRO
+
+#else
+static JSRuntime *gRuntime = NULL;
+
+/* 
+ * Since signal handlers can't block, we must disable them before manipulating
+ * the context list.
+ */
+
+#define WITH_LOCKED_CONTEXT_LIST(x)             \
+    JS_BEGIN_MACRO                              \
+        StopWatchdog(gRuntime);                 \
+        x;                                      \
+        StartWatchdog(gRuntime);                \
+    JS_END_MACRO
+
 #endif
 
 int gExitCode = 0;
 JSBool gQuitting = JS_FALSE;
 FILE *gErrFile = NULL;
 FILE *gOutFile = NULL;
 
 static JSBool reportWarnings = JS_TRUE;
@@ -206,129 +245,53 @@ GetLine(FILE *file, const char * prompt)
         return buffer;
     free(buffer);
     return NULL;
 }
 
 /*
  * State to store as JSContext private.
  *
- * In the JS_THREADSAFE case, when the watchdog thread triggers the operation
- * callback, we use PR_IntervalNow(), not JS_Now() as the latter could be
- * expensive and is not suitable for calls when a GC lock is held. This forces
- * us to use PRIntervalTime as a time type and deal with potential time-wraps
- * over uint32 limit. In particular, we must use time relative to some recent
- * timestamp when checking for expiration, not absolute time values, as in the
- * !JS_THREADSAFE case, when time is int64 and no time-wraps are feasible.
- *
- * We declare such timestamps as volatile as they are updated in the operation
+ * We declare such timestamp as volatile as they are updated in the operation
  * callback without taking any locks. Any possible race can only lead to more
  * frequent callback calls. This is safe as the callback does everything based
  * on timing.
  */
 struct JSShellContextData {
-#ifdef JS_THREADSAFE
-    PRIntervalTime          timeout;
-    volatile PRIntervalTime startTime;      /* startTime + timeout is time when
-                                               script must be stopped */
-    PRIntervalTime          yieldPeriod;
-    volatile PRIntervalTime lastYieldTime;  /* lastYieldTime + yieldPeriod is
-                                               the time to call
-                                               JS_YieldRequest() */
-#else
-    int64                   stopTime;       /* time when script must be
-                                               stopped */
-#endif
+    volatile JSIntervalTime startTime;
 };
 
-static JSBool
-SetTimeoutValue(JSContext *cx, jsdouble t);
-
-#ifdef JS_THREADSAFE
-
-# define DEFAULT_YIELD_PERIOD()     (PR_TicksPerSecond() / 50)
-
-/*
- * The function assumes that the GC lock is already held on entry. On a
- * successful exit the lock will be held, on failure the lock is released and
- * the error is reported.
- */
-static JSBool
-RescheduleWatchdog(JSContext *cx, JSShellContextData *data, PRIntervalTime now);
-
-#else
-
-const int64 MICROSECONDS_PER_SECOND = 1000000LL;
-const int64 MAX_TIME_VALUE = 0x7FFFFFFFFFFFFFFFLL;
-
-#endif
-
 static JSShellContextData *
 NewContextData()
 {
     JSShellContextData *data = (JSShellContextData *)
-                               malloc(sizeof(JSShellContextData));
+                               calloc(sizeof(JSShellContextData), 1);
     if (!data)
         return NULL;
-#ifdef JS_THREADSAFE
-    data->timeout = PR_INTERVAL_NO_TIMEOUT;
-    data->yieldPeriod = PR_INTERVAL_NO_TIMEOUT;
-# ifdef DEBUG
-    data->startTime = 0;
-    data->lastYieldTime = 0;
-# endif
-#else /* !JS_THREADSAFE */
-    data->stopTime = MAX_TIME_VALUE;
-#endif
-
+    data->startTime = js_IntervalNow();
     return data;
 }
 
 static inline JSShellContextData *
 GetContextData(JSContext *cx)
 {
     JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx);
 
     JS_ASSERT(data);
     return data;
 }
 
 static JSBool
 ShellOperationCallback(JSContext *cx)
 {
-    JSShellContextData *data = GetContextData(cx);
-    JSBool doStop;
-#ifdef JS_THREADSAFE
-    JSBool doYield;
-    PRIntervalTime now = PR_IntervalNow();
-
-    doStop = (data->timeout != PR_INTERVAL_NO_TIMEOUT &&
-              now - data->startTime >= data->timeout);
-
-    doYield = (data->yieldPeriod != PR_INTERVAL_NO_TIMEOUT &&
-               now - data->lastYieldTime >= data->yieldPeriod);
-    if (doYield)
-        data->lastYieldTime = now;
-
-#else /* !JS_THREADSAFE */
-    int64 now = JS_Now();
-
-    doStop = (now >= data->stopTime);
-#endif
-
-    if (doStop) {
-        fputs("Error: script is running for too long\n", stderr);
-        return JS_FALSE;
+    JSShellContextData *data;
+    if ((data = GetContextData(cx)) != NULL) {
+        /* If we spent too much time in this script, abort it. */
+        return !gOperationLimit || (uint32(js_IntervalNow() - data->startTime) < gOperationLimit);
     }
-
-#ifdef JS_THREADSAFE
-    if (doYield)
-        JS_YieldRequest(cx);
-#endif
-
     return JS_TRUE;
 }
 
 static void
 SetContextOptions(JSContext *cx)
 {
     jsuword stackLimit;
 
@@ -341,18 +304,17 @@ SetContextOptions(JSContext *cx)
 #if JS_STACK_GROWTH_DIRECTION > 0
         stackLimit = gStackBase + gMaxStackSize;
 #else
         stackLimit = gStackBase - gMaxStackSize;
 #endif
     }
     JS_SetThreadStackLimit(cx, stackLimit);
     JS_SetScriptStackQuota(cx, gScriptStackQuota);
-    SetTimeoutValue(cx, gOperationTimeout);
-    JS_SetOperationCallbackFunction(cx, ShellOperationCallback);
+    JS_SetOperationCallback(cx, ShellOperationCallback);
 }
 
 static void
 Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY)
 {
     JSBool ok, hitEOF;
     JSScript *script;
     jsval result;
@@ -710,18 +672,17 @@ extern void js_InitJITStatsClass(JSConte
                 obj = gobj;
             }
             break;
 
         case 't':
             if (++i == argc)
                 return usage();
 
-            gOperationTimeout = atof(argv[i]);
-            if (!SetTimeoutValue(cx, gOperationTimeout))
+            if (!SetTimeoutValue(cx, atof(argv[i])))
                 return JS_FALSE;
 
             break;
 
         case 'c':
             /* set stack chunk size */
             gStackChunkSize = atoi(argv[++i]);
             break;
@@ -2755,17 +2716,19 @@ EvalInContext(JSContext *cx, JSObject *o
     JSBool lazy, ok;
     jsval v;
     JSStackFrame *fp;
 
     sobj = NULL;
     if (!JS_ConvertArguments(cx, argc, argv, "S / o", &str, &sobj))
         return JS_FALSE;
 
-    scx = JS_NewContext(JS_GetRuntime(cx), gStackChunkSize);
+    WITH_LOCKED_CONTEXT_LIST(
+        scx = JS_NewContext(JS_GetRuntime(cx), gStackChunkSize)
+    );
     if (!scx) {
         JS_ReportOutOfMemory(cx);
         return JS_FALSE;
     }
     JS_SetOptions(scx, JS_GetOptions(cx));
 
 #ifdef JS_THREADSAFE
     JS_BeginRequest(scx);
@@ -2810,17 +2773,19 @@ EvalInContext(JSContext *cx, JSObject *o
                 JS_ReportOutOfMemory(cx);
         }
     }
 
 out:
 #ifdef JS_THREADSAFE
     JS_EndRequest(scx);
 #endif
-    JS_DestroyContextNoGC(scx);
+    WITH_LOCKED_CONTEXT_LIST(
+        JS_DestroyContextNoGC(scx)
+    );
     return ok;
 }
 
 static int32 JS_FASTCALL
 ShapeOf_tn(JSObject *obj)
 {
     if (!obj)
         return 0;
@@ -2835,16 +2800,21 @@ ShapeOf(JSContext *cx, uintN argc, jsval
     jsval v = JS_ARGV(cx, vp)[0];
     if (!JSVAL_IS_OBJECT(v)) {
         JS_ReportError(cx, "shapeOf: object expected");
         return JS_FALSE;
     }
     return JS_NewNumberValue(cx, ShapeOf_tn(JSVAL_TO_OBJECT(v)), vp);
 }
 
+static void
+Callback(JSRuntime *rt)
+{
+}
+
 #ifdef JS_THREADSAFE
 
 static JSBool
 Sleep_fn(JSContext *cx, uintN argc, jsval *vp)
 {
     jsdouble t_secs;
     PRUint32 t_ticks;
     jsrefcount rc;
@@ -2890,72 +2860,43 @@ struct ScatterThreadData {
     jsval               fn;
 };
 
 static void
 DoScatteredWork(JSContext *cx, ScatterThreadData *td)
 {
     jsval *rval = &td->shared->results[td->index];
 
-    JSShellContextData *data = GetContextData(cx);
-    PRIntervalTime oldYieldPeriod = data->yieldPeriod;
-    PRIntervalTime newYieldPeriod = DEFAULT_YIELD_PERIOD();
-    JSBool scheduleOk = JS_TRUE;
-
-    /*
-     * Here oldYieldPeriod is DEFAULT_YIELD_PERIOD() when the scatter reuses
-     * a context used by a previous scatter call.
-     */
-    if (oldYieldPeriod != newYieldPeriod) {
-        JS_LOCK_GC(cx->runtime);
-        PRIntervalTime now = PR_IntervalNow();
-        JS_ASSERT(oldYieldPeriod == PR_INTERVAL_NO_TIMEOUT);
-        data->lastYieldTime = now;
-        data->yieldPeriod = newYieldPeriod;
-        scheduleOk = RescheduleWatchdog(cx, data, now);
-        if (scheduleOk)
-            JS_UNLOCK_GC(cx->runtime);
-    }
-    if (!scheduleOk ||
-        !JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
+    if (!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
         *rval = JSVAL_VOID;
         JS_GetPendingException(cx, rval);
         JS_ClearPendingException(cx);
     }
-
-    /*
-     * We do not need to lock or call RescheduleWatchdog. Here yieldPeriod
-     * can only stay at DEFAULT_YIELD_PERIOD or go to PR_INTERVAL_NO_TIMEOUT.
-     * Thus we never need to wake up the watchdog thread earlier.
-     */
-    JS_ASSERT(oldYieldPeriod == data->yieldPeriod ||
-              oldYieldPeriod == PR_INTERVAL_NO_TIMEOUT);
-    data->yieldPeriod = oldYieldPeriod;
 }
 
 static void
 RunScatterThread(void *arg)
 {
     ScatterThreadData *td;
     ScatterStatus st;
     JSContext *cx;
 
     td = (ScatterThreadData *)arg;
     cx = td->cx;
 
-    /* Wait for go signal. */
+    /* Wait for our signal. */
     PR_Lock(td->shared->lock);
     while ((st = td->shared->status) == SCATTER_WAIT)
         PR_WaitCondVar(td->shared->cvar, PR_INTERVAL_NO_TIMEOUT);
     PR_Unlock(td->shared->lock);
 
     if (st == SCATTER_CANCEL)
         return;
 
-    /* We are go. */
+    /* We are good to go. */
     JS_SetContextThread(cx);
     JS_SetThreadStackLimit(cx, 0);
     JS_BeginRequest(cx);
     DoScatteredWork(cx, td);
     JS_EndRequest(cx);
     JS_ClearContextThread(cx);
 }
 
@@ -3038,17 +2979,20 @@ Scatter(JSContext *cx, uintN argc, jsval
                 JS_RemoveRoot(cx, &sd.threads[i].fn);
             free(sd.threads);
             sd.threads = NULL;
             goto fail;
         }
     }
 
     for (i = 1; i < n; i++) {
-        JSContext *newcx = JS_NewContext(JS_GetRuntime(cx), 8192);
+        JSContext *newcx;
+        WITH_LOCKED_CONTEXT_LIST(
+            newcx = JS_NewContext(JS_GetRuntime(cx), 8192)
+        );
         if (!newcx)
             goto fail;
         JS_SetGlobalObject(newcx, JS_GetGlobalObject(cx));
         JS_ClearContextThread(newcx);
         sd.threads[i].cx = newcx;
     }
 
     for (i = 1; i < n; i++) {
@@ -3096,17 +3040,19 @@ out:
     if (sd.threads) {
         JSContext *acx;
 
         for (i = 0; i < n; i++) {
             JS_RemoveRoot(cx, &sd.threads[i].fn);
             acx = sd.threads[i].cx;
             if (acx) {
                 JS_SetContextThread(acx);
-                JS_DestroyContext(acx);
+                WITH_LOCKED_CONTEXT_LIST(
+                    JS_DestroyContext(acx)
+                );
             }
         }
         free(sd.threads);
     }
     if (sd.results) {
         for (i = 0; i < n; i++)
             JS_RemoveRoot(cx, &sd.results[i]);
         free(sd.results);
@@ -3118,228 +3064,203 @@ out:
 
     return ok;
 
 fail:
     ok = JS_FALSE;
     goto out;
 }
 
-/*
- * Find duration between now and base + period, set it to sleepDuration if the
- * latter value is greater and set expired to true if base + period comes
- * before now. This function correctly deals with a possible time wrap between
- * base and now.
- */
-static void
-UpdateSleepDuration(PRIntervalTime now, PRIntervalTime base,
-                    PRIntervalTime period, PRIntervalTime &sleepDuration,
-                    JSBool &expired)
-{
-    if (period == PR_INTERVAL_NO_TIMEOUT)
-        return;
-
-    PRIntervalTime t;
-    PRIntervalTime diff = now - base;
-    if (diff >= period) {
-        expired = JS_TRUE;
-        t = period;
-    } else {
-        t = period - diff;
-    }
-    if (sleepDuration == PR_INTERVAL_NO_TIMEOUT || sleepDuration > t)
-        sleepDuration = t;
-}
-
-static void
-CheckCallbackTime(JSContext *cx, JSShellContextData *data, PRIntervalTime now,
-                  PRIntervalTime &sleepDuration)
-{
-    JSBool expired = JS_FALSE;
-
-    UpdateSleepDuration(now, data->startTime, data->timeout,
-                        sleepDuration, expired);
-    UpdateSleepDuration(now, data->lastYieldTime, data->yieldPeriod,
-                        sleepDuration, expired);
-    if (expired) {
-        JS_ASSERT(sleepDuration != PR_INTERVAL_NO_TIMEOUT);
-        JS_TriggerOperationCallback(cx);
-    }
-}
-
 static void
 WatchdogMain(void *arg)
 {
     JSRuntime *rt = (JSRuntime *) arg;
 
     JS_LOCK_GC(rt);
     while (gWatchdogThread) {
-        PRIntervalTime now = PR_IntervalNow();
-        PRIntervalTime sleepDuration = PR_INTERVAL_NO_TIMEOUT;
-        JSContext *iter = NULL;
-        JSContext *acx;
-
-        while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) {
-            if (acx->requestDepth > 0) {
-                JSShellContextData *data = (JSShellContextData *)
-                                           JS_GetContextPrivate(acx);
-
-                /*
-                 * For the last context inside JS_DestroyContext the engine
-                 * starts a new request to shutdown the runtime. For such
-                 * context data is null.
-                 */
-                if (data)
-                    CheckCallbackTime(acx, data, now, sleepDuration);
-            }
-        }
-
-        gLastWatchdogWakeup = now;
-        gWatchdogSleepDuration = sleepDuration;
+        JSContext *acx = NULL;
+    
+        while ((acx = js_NextActiveContext(rt, acx)))
+            JS_TriggerOperationCallback(acx);
+
 #ifdef DEBUG
         PRStatus status =
 #endif
-            PR_WaitCondVar(gWatchdogWakeup, sleepDuration);
+        /* Trigger the operation callbacks every second. */
+        PR_WaitCondVar(gWatchdogWakeup, PR_SecondsToInterval(1));
         JS_ASSERT(status == PR_SUCCESS);
     }
-
     /* Wake up the main thread waiting for the watchdog to terminate. */
     PR_NotifyCondVar(gWatchdogWakeup);
     JS_UNLOCK_GC(rt);
 }
 
 static JSBool
-RescheduleWatchdog(JSContext *cx, JSShellContextData *data, PRIntervalTime now)
+StartWatchdog(JSRuntime *rt)
 {
-    JS_ASSERT(data == GetContextData(cx));
-
-    PRIntervalTime nextCallbackTime = PR_INTERVAL_NO_TIMEOUT;
-    CheckCallbackTime(cx, data, now, nextCallbackTime);
-    if (nextCallbackTime == PR_INTERVAL_NO_TIMEOUT)
+    if (gWatchdogThread || !gOperationLimit)
         return JS_TRUE;
-
+    
+    JS_LOCK_GC(rt);
+    gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
+                                      WatchdogMain,
+                                      rt,
+                                      PR_PRIORITY_NORMAL,
+                                      PR_LOCAL_THREAD,
+                                      PR_UNJOINABLE_THREAD,
+                                      0);
+    if (!gWatchdogThread) {
+        JS_UNLOCK_GC(rt);
+        return JS_FALSE;
+    }
+    JS_UNLOCK_GC(rt);
+    return JS_TRUE;
+}
+
+static void
+StopWatchdog(JSRuntime *rt)
+{
+    JS_LOCK_GC(rt);
     if (gWatchdogThread) {
         /*
-         * Notify the watchdog if it would wake up after data->watchdogLimit
-         * expires. PRIntervalTime is unsigned so the subtraction in the
-         * following check gives the correct interval even when time wraps
-         * around between gLastWatchdogWakeup and now.
+         * The watchdog thread is running, tell it to terminate waking it up
+         * if necessary and wait until it signals that it done.
          */
-        if (gWatchdogSleepDuration == PR_INTERVAL_NO_TIMEOUT ||
-            PRInt32(now - gLastWatchdogWakeup) <
-            PRInt32(gWatchdogSleepDuration) - PRInt32(nextCallbackTime)) {
-            PR_NotifyCondVar(gWatchdogWakeup);
-        }
-    } else {
-        gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
-                                          WatchdogMain,
-                                          cx->runtime,
-                                          PR_PRIORITY_NORMAL,
-                                          PR_LOCAL_THREAD,
-                                          PR_UNJOINABLE_THREAD,
-                                          0);
-        if (!gWatchdogThread) {
-            JS_UNLOCK_GC(cx->runtime);
-            JS_ReportError(cx, "failed to create the watchdog thread");
-            return JS_FALSE;
-        }
-
-        /* The watchdog thread does not sleep on creation. */
-        JS_ASSERT(gWatchdogSleepDuration == 0);
-        gLastWatchdogWakeup = now;
+        gWatchdogThread = NULL;
+        PR_NotifyCondVar(gWatchdogWakeup);
+        PR_WaitCondVar(gWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
     }
+    JS_UNLOCK_GC(rt);
+    JS_DESTROY_CONDVAR(gWatchdogWakeup);
+}
+
+#else
+
+static void
+WatchdogHandler(int sig)
+{
+    JSRuntime *rt = gRuntime;
+    JSContext *acx = NULL;
+    
+    while ((acx = js_NextActiveContext(rt, acx)))
+        JS_TriggerOperationCallback(acx);
+
+#ifndef XP_WIN
+    alarm(1);
+#endif
+}
+
+#ifdef XP_WIN
+static HANDLE gTimerHandle = 0;
+
+VOID CALLBACK TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
+{
+    WatchdogHandler(0);
+}
+#endif
+
+static JSBool
+StartWatchdog(JSRuntime *rt)
+{
+    if (!gOperationLimit)
+        return JS_TRUE;
+
+#ifdef XP_WIN
+    JS_ASSERT(gTimerHandle == 0);
+    if (!CreateTimerQueueTimer(&gTimerHandle,
+                               NULL,
+                               (WAITORTIMERCALLBACK)TimerCallback,
+                               NULL,
+                               1000,
+                               1000,
+                               WT_EXECUTEINTIMERTHREAD))
+        return JS_FALSE;
+#else
+    signal(SIGALRM, WatchdogHandler); /* set the Alarm signal capture */
+    alarm(1);
+#endif    
+    
     return JS_TRUE;
 }
+
+static void
+StopWatchdog(JSRuntime *rt)
+{
+#ifdef XP_WIN
+    DeleteTimerQueueTimer(NULL, gTimerHandle, NULL);
+    gTimerHandle = 0;
+#else
+    alarm(0);
+    signal(SIGALRM, NULL);
+#endif
+}
+
 #endif /* JS_THREADSAFE */
 
 static JSBool
 SetTimeoutValue(JSContext *cx, jsdouble t)
 {
     /* NB: The next condition also filter out NaNs. */
     if (!(t <= 3600.0)) {
         JS_ReportError(cx, "Excessive timeout value");
         return JS_FALSE;
     }
 
-    JSShellContextData *data = GetContextData(cx);
-#ifdef JS_THREADSAFE
-    JS_LOCK_GC(cx->runtime);
-    if (t < 0) {
-        data->timeout = PR_INTERVAL_NO_TIMEOUT;
-    } else {
-        PRIntervalTime now = PR_IntervalNow();
-        data->timeout = PRIntervalTime(t * PR_TicksPerSecond());
-        data->startTime = now;
-        if (!RescheduleWatchdog(cx, data, now)) {
-            /* The GC lock is already released here. */
-            return JS_FALSE;
-        }
+    gOperationLimit = (t > 0) ? JSInt64(t*1000) : 0;
+
+    if (!StartWatchdog(cx->runtime)) {
+        JS_ReportError(cx, "failed to create the watchdog");
+        return JS_FALSE;
     }
-    JS_UNLOCK_GC(cx->runtime);
-
-#else /* !JS_THREADSAFE */
-    if (t < 0) {
-        data->stopTime = MAX_TIME_VALUE;
-        JS_SetOperationLimit(cx, JS_MAX_OPERATION_LIMIT);
-    } else {
-        int64 now = JS_Now();
-        data->stopTime = now + int64(t * MICROSECONDS_PER_SECOND);
-
-        /*
-         * Call the callback infrequently enough to avoid the overhead of
-         * time calculations there.
-         */
-        JS_SetOperationLimit(cx, 1000 * JS_OPERATION_WEIGHT_BASE);
-    }
-#endif
+    
     return JS_TRUE;
 }
 
+static double
+GetTimeoutValue(JSContext *cx)
+{
+    if (!gOperationLimit)
+        return -1;
+    
+    return gOperationLimit/PRMJ_USEC_PER_MSEC;
+}
+
 static JSBool
 Timeout(JSContext *cx, uintN argc, jsval *vp)
 {
-    if (argc == 0) {
-        JSShellContextData *data = GetContextData(cx);
-        jsdouble t; /* remaining time to run */
-
-#ifdef JS_THREADSAFE
-        if (data->timeout == PR_INTERVAL_NO_TIMEOUT) {
-            t = -1.0;
-        } else {
-            PRIntervalTime expiredTime = PR_IntervalNow() - data->startTime;
-            t = (expiredTime >= data->timeout)
-                ? 0.0
-                : jsdouble(data->timeout - expiredTime) / PR_TicksPerSecond();
-        }
-#else
-        if (data->stopTime == MAX_TIME_VALUE) {
-            t = -1.0;
-        } else {
-            int64 remainingTime = data->stopTime - JS_Now();
-            t = (remainingTime <= 0)
-                ? 0.0
-                : jsdouble(remainingTime) / MICROSECONDS_PER_SECOND;
-        }
-#endif
-        return JS_NewNumberValue(cx, t, vp);
-    }
+    if (argc == 0)
+        return JS_NewNumberValue(cx, GetTimeoutValue(cx), vp);
 
     if (argc > 1) {
         JS_ReportError(cx, "Wrong number of arguments");
         return JS_FALSE;
     }
 
     jsdouble t;
     if (!JS_ValueToNumber(cx, JS_ARGV(cx, vp)[0], &t))
         return JS_FALSE;
 
     *vp = JSVAL_VOID;
     return SetTimeoutValue(cx, t);
 }
 
+static JSBool
+Elapsed(JSContext *cx, uintN argc, jsval *vp)
+{
+    if (argc == 0) {
+        double d = 0.0;
+        JSShellContextData *data = GetContextData(cx);
+        if (data)
+            d = js_IntervalNow() - data->startTime;
+        return JS_NewNumberValue(cx, d, vp);
+    }
+    JS_ReportError(cx, "Wrong number of arguments");
+    return JS_FALSE;
+}
+
 JS_DEFINE_TRCINFO_1(Print, (2, (static, JSVAL_FAIL, Print_tn, CONTEXT, STRING, 0, 0)))
 JS_DEFINE_TRCINFO_1(ShapeOf, (1, (static, INT32, ShapeOf_tn, OBJECT, 0, 0)))
 
 #ifdef XP_UNIX
 
 #include <fcntl.h>
 #include <sys/stat.h>
 
@@ -3524,16 +3445,17 @@ static JSFunctionSpec shell_functions[] 
     JS_FS("arrayInfo",      js_ArrayInfo,       1,0,0),
 #endif
 #ifdef JS_THREADSAFE
     JS_FN("sleep",          Sleep_fn,       1,0),
     JS_FN("scatter",        Scatter,        1,0),
 #endif
     JS_FS("snarf",          Snarf,        0,0,0),
     JS_FN("timeout",        Timeout,        1,0),
+    JS_FN("elapsed",        Elapsed,        0,0),
     JS_FS_END
 };
 
 static const char shell_help_header[] =
 "Command                  Description\n"
 "=======                  ===========\n";
 
 static const char *const shell_help_messages[] = {
@@ -3615,16 +3537,17 @@ static const char *const shell_help_mess
 #ifdef JS_THREADSAFE
 "sleep(dt)                Sleep for dt seconds",
 "scatter(fns)             Call functions concurrently (ignoring errors)",
 #endif
 "snarf(filename)          Read filename into returned string",
 "timeout([seconds])\n"
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  A negative value (default) means that the execution time is unlimited.",
+"elapsed()                Execution time elapsed for the current context.\n",
 };
 
 /* Help messages must match shell functions. */
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) + 1 ==
                  JS_ARRAY_LENGTH(shell_functions));
 
 #ifdef DEBUG
 static void
@@ -4383,30 +4306,30 @@ Evaluate(JSContext *cx, JSObject *obj, u
     return ok;
 }
 
 #endif /* NARCISSUS */
 
 static JSBool
 ContextCallback(JSContext *cx, uintN contextOp)
 {
+    JSShellContextData *data;
+    
     switch (contextOp) {
       case JSCONTEXT_NEW: {
-        JSShellContextData *data = NewContextData();
+        data = NewContextData();
         if (!data)
             return JS_FALSE;
         JS_SetContextPrivate(cx, data);
         JS_SetErrorReporter(cx, my_ErrorReporter);
         JS_SetVersion(cx, JSVERSION_LATEST);
         SetContextOptions(cx);
         break;
-      }
-
-      case JSCONTEXT_DESTROY: {
-        JSShellContextData *data = GetContextData(cx);
+      case JSCONTEXT_DESTROY:
+        data = GetContextData(cx);
         JS_SetContextPrivate(cx, NULL);
         free(data);
         break;
       }
 
       default:
         break;
     }
@@ -4457,21 +4380,25 @@ main(int argc, char **argv, char **envp)
     rt = JS_NewRuntime(64L * 1024L * 1024L);
     if (!rt)
         return 1;
 
 #ifdef JS_THREADSAFE
     gWatchdogWakeup = JS_NEW_CONDVAR(rt->gcLock);
     if (!gWatchdogWakeup)
         return 1;
-#endif
+#else
+    gRuntime = rt;
+#endif    
 
     JS_SetContextCallback(rt, ContextCallback);
 
-    cx = JS_NewContext(rt, gStackChunkSize);
+    WITH_LOCKED_CONTEXT_LIST(
+        cx = JS_NewContext(rt, gStackChunkSize)
+    );
     if (!cx)
         return 1;
 
 #ifdef JS_THREADSAFE
     JS_BeginRequest(cx);
 #endif
 
     glob = JS_NewObject(cx, &global_class, NULL, NULL);
@@ -4566,29 +4493,18 @@ main(int argc, char **argv, char **envp)
         JSD_DebuggerOff(jsdc);
     }
 #endif  /* JSDEBUGGER */
 
 #ifdef JS_THREADSAFE
     JS_EndRequest(cx);
 #endif
 
-    JS_DestroyContext(cx);
-
-#ifdef JS_THREADSAFE
-    JS_LOCK_GC(rt);
-    if (gWatchdogThread) {
-        /*
-         * The watchdog thread is running, tell it to terminate waking it up
-         * if necessary and wait until it signals that it done.
-         */
-        gWatchdogThread = NULL;
-        PR_NotifyCondVar(gWatchdogWakeup);
-        PR_WaitCondVar(gWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
-    }
-    JS_UNLOCK_GC(rt);
-    JS_DESTROY_CONDVAR(gWatchdogWakeup);
-#endif
+    WITH_LOCKED_CONTEXT_LIST( 
+        JS_DestroyContext(cx)
+    );
+
+    StopWatchdog(rt);
 
     JS_DestroyRuntime(rt);
     JS_ShutDown();
     return result;
 }
--- a/js/src/xpconnect/src/xpccomponents.cpp
+++ b/js/src/xpconnect/src/xpccomponents.cpp
@@ -3399,34 +3399,32 @@ ContextHolder::ContextHolder(JSContext *
 {
     if(mJSContext)
     {
         JS_SetOptions(mJSContext,
                       JSOPTION_DONT_REPORT_UNCAUGHT |
                       JSOPTION_PRIVATE_IS_NSISUPPORTS);
         JS_SetGlobalObject(mJSContext, aSandbox);
         JS_SetContextPrivate(mJSContext, this);
-        JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback,
-                                JS_GetOperationLimit(aOuterCx));
+        JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback);
     }
 }
 
 JSBool
 ContextHolder::ContextHolderOperationCallback(JSContext *cx)
 {
     ContextHolder* thisObject =
         static_cast<ContextHolder*>(JS_GetContextPrivate(cx));
     NS_ASSERTION(thisObject, "How did that happen?");
 
     JSContext *origCx = thisObject->mOrigCx;
     JSOperationCallback callback = JS_GetOperationCallback(origCx);
     JSBool ok = JS_TRUE;
     if(callback)
         ok = callback(origCx);
-    JS_SetOperationLimit(cx, JS_GetOperationLimit(origCx));
     return ok;
 }
 
 /***************************************************************************/
 
 /* void evalInSandbox(in AString source, in nativeobj sandbox); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::EvalInSandbox(const nsAString &source)
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -788,16 +788,59 @@ JSBool XPCJSRuntime::GCCallback(JSContex
             default:
                 break;
         }
     }
 
     return JS_TRUE;
 }
 
+// Auto JS GC lock helper.
+class AutoLockJSGC
+{
+public:
+    AutoLockJSGC(JSRuntime* rt) : mJSRuntime(rt) { JS_LOCK_GC(mJSRuntime); }
+    ~AutoLockJSGC() { JS_UNLOCK_GC(mJSRuntime); }
+private:
+    JSRuntime* mJSRuntime;
+
+    // Disable copy or assignment semantics.
+    AutoLockJSGC(const AutoLockJSGC&);
+    void operator=(const AutoLockJSGC&);
+};
+
+//static
+void
+XPCJSRuntime::WatchdogMain(void *arg)
+{
+    XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
+
+    // Lock lasts until we return
+    AutoLockJSGC lock(self->mJSRuntime);
+
+    while (self->mWatchdogThread)
+    {
+#ifdef DEBUG
+        PRStatus status =
+#endif
+            PR_WaitCondVar(self->mWatchdogWakeup, PR_TicksPerSecond());
+        JS_ASSERT(status == PR_SUCCESS);
+
+        JSContext* cx = nsnull;
+        while((cx = js_NextActiveContext(self->mJSRuntime, cx)))
+        {
+            JS_TriggerOperationCallback(cx);
+        }
+    }
+
+    /* Wake up the main thread waiting for the watchdog to terminate. */
+    PR_NotifyCondVar(self->mWatchdogWakeup);
+}
+
+
 /***************************************************************************/
 
 #ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
 static JSDHashOperator
 DEBUG_WrapperChecker(JSDHashTable *table, JSDHashEntryHdr *hdr,
                      uint32 number, void *arg)
 {
     XPCWrappedNative* wrapper = (XPCWrappedNative*)((JSDHashEntryStub*)hdr)->key;
@@ -836,16 +879,34 @@ void XPCJSRuntime::SystemIsBeingShutDown
 
     if(mDetachedWrappedNativeProtoMap)
         mDetachedWrappedNativeProtoMap->
             Enumerate(DetachedWrappedNativeProtoShutdownMarker, cx);
 }
 
 XPCJSRuntime::~XPCJSRuntime()
 {
+    if (mWatchdogWakeup)
+    {
+        // If the watchdog thread is running, tell it to terminate waking it
+        // up if necessary and wait until it signals that it finished. As we
+        // must release the lock before calling PR_DestroyCondVar, we use an
+        // extra block here.
+        {
+            AutoLockJSGC lock(mJSRuntime);
+            if (mWatchdogThread) {
+                mWatchdogThread = nsnull;
+                PR_NotifyCondVar(mWatchdogWakeup);
+                PR_WaitCondVar(mWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
+            }
+        }
+        PR_DestroyCondVar(mWatchdogWakeup);
+        mWatchdogWakeup = nsnull;
+    }
+
 #ifdef XPC_DUMP_AT_SHUTDOWN
     {
     // count the total JSContexts in use
     JSContext* iter = nsnull;
     int count = 0;
     while(JS_ContextIterator(mJSRuntime, &iter))
         count ++;
     if(count)
@@ -1009,17 +1070,19 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
    mMapLock(XPCAutoLock::NewLock("XPCJSRuntime::mMapLock")),
    mThreadRunningGC(nsnull),
    mWrappedJSToReleaseArray(),
    mNativesToReleaseArray(),
    mDoingFinalization(JS_FALSE),
    mVariantRoots(nsnull),
    mWrappedJSRoots(nsnull),
    mObjectHolderRoots(nsnull),
-   mUnrootedGlobalCount(0)
+   mUnrootedGlobalCount(0),
+   mWatchdogWakeup(nsnull),
+   mWatchdogThread(nsnull)
 {
 #ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
     DEBUG_WrappedNativeHashtable =
         JS_NewDHashTable(JS_DHashGetStubOps(), nsnull,
                          sizeof(JSDHashEntryStub), 128);
 #endif
 
     DOM_InitInterfaces();
@@ -1052,27 +1115,34 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
         // finite threshold (0xffffffff is infinity for uint32 parameters).
         // This leaves the maximum-JS_malloc-bytes threshold still in effect
         // to cause period, and we hope hygienic, last-ditch GCs from within
         // the GC's allocator.
         JS_SetGCParameter(mJSRuntime, JSGC_MAX_BYTES, 0xffffffff);
         JS_SetContextCallback(mJSRuntime, ContextCallback);
         JS_SetGCCallbackRT(mJSRuntime, GCCallback);
         JS_SetExtraGCRoots(mJSRuntime, TraceJS, this);
+        mWatchdogWakeup = JS_NEW_CONDVAR(mJSRuntime->gcLock);
     }
 
     if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,
                           sizeof(ObjectHolder), 512))
         mJSHolders.ops = nsnull;
 
     // Install a JavaScript 'debugger' keyword handler in debug builds only
 #ifdef DEBUG
     if(mJSRuntime && !JS_GetGlobalDebugHooks(mJSRuntime)->debuggerHandler)
         xpc_InstallJSDebuggerKeywordHandler(mJSRuntime);
 #endif
+
+    AutoLockJSGC lock(mJSRuntime);
+
+    mWatchdogThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
+                                      PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
+                                      PR_UNJOINABLE_THREAD, 0);
 }
 
 // static
 XPCJSRuntime*
 XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect)
 {
     NS_PRECONDITION(aXPConnect,"bad param");
 
@@ -1084,17 +1154,18 @@ XPCJSRuntime::newXPCJSRuntime(nsXPConnec
        self->GetWrappedJSClassMap()          &&
        self->GetIID2NativeInterfaceMap()     &&
        self->GetClassInfo2NativeSetMap()     &&
        self->GetNativeSetMap()               &&
        self->GetThisTranslatorMap()          &&
        self->GetNativeScriptableSharedMap()  &&
        self->GetDyingWrappedNativeProtoMap() &&
        self->GetExplicitNativeWrapperMap()   &&
-       self->GetMapLock())
+       self->GetMapLock()                    &&
+       self->mWatchdogThread)
     {
         return self;
     }
     delete self;
     return nsnull;
 }
 
 JSBool
@@ -1254,35 +1325,35 @@ XPCJSRuntime::DebugDump(PRInt16 depth)
 }
 
 /***************************************************************************/
 
 void
 XPCRootSetElem::AddToRootSet(JSRuntime* rt, XPCRootSetElem** listHead)
 {
     NS_ASSERTION(!mSelfp, "Must be not linked");
-    JS_LOCK_GC(rt);
+
+    AutoLockJSGC lock(rt);
     mSelfp = listHead;
     mNext = *listHead;
     if(mNext)
     {
         NS_ASSERTION(mNext->mSelfp == listHead, "Must be list start");
         mNext->mSelfp = &mNext;
     }
     *listHead = this;
-    JS_UNLOCK_GC(rt);
 }
 
 void
 XPCRootSetElem::RemoveFromRootSet(JSRuntime* rt)
 {
     NS_ASSERTION(mSelfp, "Must be linked");
-    JS_LOCK_GC(rt);
+
+    AutoLockJSGC lock(rt);
     NS_ASSERTION(*mSelfp == this, "Link invariant");
     *mSelfp = mNext;
     if(mNext)
         mNext->mSelfp = mSelfp;
-    JS_UNLOCK_GC(rt);
 #ifdef DEBUG
     mSelfp = nsnull;
     mNext = nsnull;
 #endif
 }
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -756,17 +756,21 @@ private:
    JSDHashTable* DEBUG_WrappedNativeHashtable;
 public:
 #endif
 
 private:
     XPCJSRuntime(); // no implementation
     XPCJSRuntime(nsXPConnect* aXPConnect);
 
-private:
+    // The caller must be holding the GC lock
+    void RescheduleWatchdog(XPCContext* ccx);
+
+    static void WatchdogMain(void *arg);
+
     static const char* mStrings[IDX_TOTAL_COUNT];
     jsid mStrIDs[IDX_TOTAL_COUNT];
     jsval mStrJSVals[IDX_TOTAL_COUNT];
 
     nsXPConnect* mXPConnect;
     JSRuntime*  mJSRuntime;
     JSObject2WrappedJSMap*   mWrappedJSMap;
     IID2WrappedJSClassMap*   mWrappedJSClassMap;
@@ -783,16 +787,18 @@ private:
     nsVoidArray mWrappedJSToReleaseArray;
     nsVoidArray mNativesToReleaseArray;
     JSBool mDoingFinalization;
     XPCRootSetElem *mVariantRoots;
     XPCRootSetElem *mWrappedJSRoots;
     XPCRootSetElem *mObjectHolderRoots;
     JSDHashTable mJSHolders;
     uintN mUnrootedGlobalCount;
+    PRCondVar *mWatchdogWakeup;
+    PRThread *mWatchdogThread;
 };
 
 /***************************************************************************/
 /***************************************************************************/
 // XPCContext is mostly a dumb class to hold JSContext specific data and
 // maps that let us find wrappers created for the given JSContext.
 
 // no virtuals