Make sure that we push a null JSContext on the current thread's XPConnect stack
authorbzbarsky@mit.edu
Mon, 09 Jul 2007 20:48:06 -0700
changeset 3282 d03b1c7f4b0acd49169613b9cdceb7578fb38634
parent 3281 305628db142c184dc749f64dcce9808ac7bd4566
child 3283 af05a179cf7b181dc98658de19a03c70c2d8cf09
push idunknown
push userunknown
push dateunknown
bugs326777
milestone1.9a7pre
Make sure that we push a null JSContext on the current thread's XPConnect stack before processing events. Bug 326777, r=bsmedberg, sr=jst
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcprivate.h
xpcom/threads/Makefile.in
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadUtilsInternal.h
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -45,18 +45,22 @@
 #include "xpcprivate.h"
 #include "XPCNativeWrapper.h"
 #include "nsBaseHashtable.h"
 #include "nsHashKeys.h"
 #include "jsatom.h"
 #include "jsfun.h"
 #include "jsobj.h"
 #include "jsscript.h"
+#include "nsThreadUtilsInternal.h"
 
-NS_IMPL_THREADSAFE_ISUPPORTS2(nsXPConnect,nsIXPConnect,nsISupportsWeakReference)
+NS_IMPL_THREADSAFE_ISUPPORTS3(nsXPConnect,
+                              nsIXPConnect,
+                              nsISupportsWeakReference,
+                              nsIThreadObserver)
 
 nsXPConnect* nsXPConnect::gSelf = nsnull;
 JSBool       nsXPConnect::gOnceAliveNowDead = JS_FALSE;
 
 const char XPC_CONTEXT_STACK_CONTRACTID[] = "@mozilla.org/js/xpc/ContextStack;1";
 const char XPC_RUNTIME_CONTRACTID[]       = "@mozilla.org/js/xpc/RuntimeService;1";
 const char XPC_EXCEPTION_CONTRACTID[]     = "@mozilla.org/js/xpc/Exception;1";
 const char XPC_CONSOLE_CONTRACTID[]       = "@mozilla.org/consoleservice;1";
@@ -281,16 +285,20 @@ nsXPConnect::GetXPConnect()
             delete gSelf;
             gSelf = nsnull;
         }
         else
         {
             // Initial extra ref to keep the singleton alive
             // balanced by explicit call to ReleaseXPConnectSingleton()
             NS_ADDREF(gSelf);
+            if (NS_FAILED(NS_SetGlobalThreadObserver(gSelf))) {
+                NS_RELEASE(gSelf);
+                // Fall through to returning null
+            }
         }
     }
     return gSelf;
 }
 
 // static
 nsXPConnect*
 nsXPConnect::GetSingleton()
@@ -302,16 +310,17 @@ nsXPConnect::GetSingleton()
 
 // static
 void
 nsXPConnect::ReleaseXPConnectSingleton()
 {
     nsXPConnect* xpc = gSelf;
     if(xpc)
     {
+        NS_SetGlobalThreadObserver(nsnull);
 
 #ifdef XPC_TOOLS_SUPPORT
         if(xpc->mProfiler)
         {
             xpc->mProfiler->Stop();
             xpc->mProfiler->WriteResults(xpc->mProfilerOutputFile);
         }
 #endif
@@ -2009,16 +2018,41 @@ nsXPConnect::FlagSystemFilenamePrefix(co
     if(NS_FAILED(rv))
         return rv;
 
     if(!JS_FlagScriptFilenamePrefix(rt, aFilenamePrefix, JSFILENAME_SYSTEM))
         return NS_ERROR_OUT_OF_MEMORY;
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXPConnect::OnProcessNextEvent(nsIThreadInternal *aThread, PRBool aMayWait,
+                                PRUint32 aRecursionDepth)
+{
+    // Push a null JSContext so that we don't see any script during
+    // event processing.
+    NS_ENSURE_STATE(mContextStack);
+    return mContextStack->Push(nsnull);
+}
+
+NS_IMETHODIMP
+nsXPConnect::AfterProcessNextEvent(nsIThreadInternal *aThread,
+                                   PRUint32 aRecursionDepth)
+{
+    NS_ENSURE_STATE(mContextStack);
+    return mContextStack->Pop(nsnull);
+}
+
+NS_IMETHODIMP
+nsXPConnect::OnDispatchedEvent(nsIThreadInternal* aThread)
+{
+    NS_NOTREACHED("Why tell us?");
+    return NS_ERROR_UNEXPECTED;
+}
+
 #ifdef DEBUG
 /* These are here to be callable from a debugger */
 JS_BEGIN_EXTERN_C
 void DumpJSStack()
 {
     nsresult rv;
     nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
     if(NS_SUCCEEDED(rv) && xpc)
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -121,16 +121,18 @@
 #ifdef XPC_USE_SECURITY_CHECKED_COMPONENT
 #include "nsISecurityCheckedComponent.h"
 #endif
 
 #ifdef XPC_TOOLS_SUPPORT
 #include "nsIXPCToolsProfiler.h"
 #endif
 
+#include "nsIThreadInternal.h"
+
 #ifdef XPC_IDISPATCH_SUPPORT
 // This goop was added because of EXCEPINFO in ThrowCOMError
 // This include is here, because it needs to occur before the undefines below
 #include <atlbase.h>
 #include "oaidl.h"
 // Nasty MS defines
 #undef GetClassInfo
 #undef GetClassName
@@ -421,24 +423,26 @@ private:
 // returned as function call result values they are not addref'd. Exceptions
 // to this rule are noted explicitly.
 
 const PRBool OBJ_IS_GLOBAL = PR_TRUE;
 const PRBool OBJ_IS_NOT_GLOBAL = PR_FALSE;
 struct JSObjectRefcounts;
 
 class nsXPConnect : public nsIXPConnect,
+                    public nsIThreadObserver,
                     public nsSupportsWeakReference,
                     public nsCycleCollectionLanguageRuntime,
                     public nsCycleCollectionParticipant
 {
 public:
     // all the interface method declarations...
     NS_DECL_ISUPPORTS
     NS_DECL_NSIXPCONNECT
+    NS_DECL_NSITHREADOBSERVER
 
     // non-interface implementation
 public:
     // These get non-addref'd pointers
     static nsXPConnect*  GetXPConnect();
     static XPCJSRuntime* GetRuntime(nsXPConnect* xpc = nsnull);
     static XPCContext*   GetContext(JSContext* cx, nsXPConnect* xpc = nsnull);
     static nsIJSRuntimeService* GetJSRuntimeService(nsXPConnect* xpc = nsnull);
--- a/xpcom/threads/Makefile.in
+++ b/xpcom/threads/Makefile.in
@@ -64,16 +64,17 @@ CPPSRCS		= \
 		nsProcessCommon.cpp \
 		nsTimerImpl.cpp \
 		TimerThread.cpp \
 		$(NULL)
 
 EXPORTS		= \
 		nsProcess.h \
 		nsEventQueue.h \
+		nsThreadUtilsInternal.h \
 		$(NULL)
 
 XPIDLSRCS	= \
 		nsIEventTarget.idl \
 		nsIThread.idl \
 		nsIThreadInternal.idl \
 		nsIThreadManager.idl \
 		nsIThreadPool.idl \
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -47,16 +47,18 @@
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo *sLog = PR_NewLogModule("nsThread");
 #endif
 #define LOG(args) PR_LOG(sLog, PR_LOG_DEBUG, args)
 
 NS_DECL_CI_INTERFACE_GETTER(nsThread)
 
+nsIThreadObserver* nsThread::sGlobalObserver;
+
 //-----------------------------------------------------------------------------
 // Because we do not have our own nsIFactory, we have to implement nsIClassInfo
 // somewhat manually.
 
 class nsThreadClassInfo : public nsIClassInfo {
 public:
   NS_DECL_ISUPPORTS_INHERITED  // no mRefCnt
   NS_DECL_NSICLASSINFO
@@ -459,16 +461,20 @@ nsThread::HasPendingEvents(PRBool *resul
 
 NS_IMETHODIMP
 nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result)
 {
   LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait, mRunningEvent));
 
   NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
 
+  if (sGlobalObserver) 
+    sGlobalObserver->OnProcessNextEvent(this, mayWait && !ShuttingDown(),
+                                        mRunningEvent);
+
   nsCOMPtr<nsIThreadObserver> obs = mObserver;
   if (obs)
     obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent);
 
   // If we are shutting down, then do not wait for new events.
   nsCOMPtr<nsIRunnable> event; 
   mEvents->GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event));
 
@@ -484,16 +490,19 @@ nsThread::ProcessNextEvent(PRBool mayWai
   } else if (mayWait) {
     NS_ASSERTION(ShuttingDown(), "This should only happen when shutting down");
     rv = NS_ERROR_UNEXPECTED;
   }
 
   if (obs)
     obs->AfterProcessNextEvent(this, mRunningEvent);
 
+  if (sGlobalObserver)
+    sGlobalObserver->AfterProcessNextEvent(this, mRunningEvent);
+
   return rv;
 }
 
 //-----------------------------------------------------------------------------
 // nsISupportsPriority
 
 NS_IMETHODIMP
 nsThread::GetPriority(PRInt32 *priority)
@@ -613,8 +622,23 @@ nsThreadSyncDispatch::Run()
   if (mSyncTask) {
     mSyncTask->Run();
     mSyncTask = nsnull;
     // unblock the origin thread
     mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
   }
   return NS_OK;
 }
+
+nsresult
+NS_SetGlobalThreadObserver(nsIThreadObserver* aObserver)
+{
+  if (aObserver && nsThread::sGlobalObserver) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!NS_IsMainThread()) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsThread::sGlobalObserver = aObserver;
+  return NS_OK;
+}
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -67,16 +67,19 @@ public:
 
   // The PRThread corresponding to this thread.
   PRThread *GetPRThread() { return mThread; }
 
   // If this flag is true, then the nsThread was created using
   // nsIThreadManager::NewThread.
   PRBool ShutdownRequired() { return mShutdownRequired; }
 
+  // The global thread observer
+  static nsIThreadObserver* sGlobalObserver;
+
 private:
   friend class nsThreadShutdownEvent;
 
   ~nsThread();
 
   PRBool ShuttingDown() { return mShutdownContext != nsnull; }
 
   PR_STATIC_CALLBACK(void) ThreadFunc(void *arg);
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/nsThreadUtilsInternal.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Boris Zbarsky <bzbarsky@mit.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsThreadUtilsInternal_h_
+#define nsThreadUtilsInternal_h_
+
+#ifdef MOZILLA_INTERNAL_API
+
+class nsIThreadObserver;
+
+/**
+ * Function to set a "global" thread observer that all threads will notify when
+ * they process an event.  This observer will not be notified when events are
+ * posted to threads.  Only one global observer can be set at a time; an
+ * attempt to change the value without setting it to null first will throw.
+ * This function does NOT take a reference to the observer; the caller of this
+ * function is responsible for setting the observer to null when it goes away.
+ * This method may only be called on the main thread; attempts to do it on
+ * other threads will return an error.
+ */
+extern nsresult
+NS_COM NS_SetGlobalThreadObserver(nsIThreadObserver* aObserver);
+
+#endif // MOZILLA_INTERNAL_API
+
+#endif  // nsThreadUtilsInternal_h_