Bug 595243 - Expose debugMode to JSD. r=gal
☠☠ backed out by a57aba6992e3 ☠ ☠
authorRobert Sayre <sayrer@gmail.com>
Fri, 29 Oct 2010 18:35:07 -0400
changeset 56767 85feecf161fe2008d2108e3e644632c0ad9c9923
parent 56754 66f4a212edebd33473bdf56408bd3af6de1c6719
child 56768 1cbf9d524b9bbd48738c7e05b4e711dbf60b0b3b
child 56776 a57aba6992e3a01fbf23de5097979a05605e309b
push idunknown
push userunknown
push dateunknown
reviewersgal
bugs595243
milestone2.0b8pre
Bug 595243 - Expose debugMode to JSD. r=gal
dom/base/nsJSEnvironment.cpp
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd_scpt.c
js/jsd/jsd_xpc.cpp
js/jsd/jsd_xpc.h
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
js/src/xpconnect/idl/nsIXPConnect.idl
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcprivate.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -988,20 +988,16 @@ nsJSContext::DOMOperationCallback(JSCont
     const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
     nsCOMPtr<jsdIExecutionHook> jsdHook;
     nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
 
     // Check if there's a user for the debugger service that's 'on' for us
     if (NS_SUCCEEDED(rv)) {
       jsds->GetDebuggerHook(getter_AddRefs(jsdHook));
       jsds->GetIsOn(&jsds_IsOn);
-      if (jsds_IsOn) { // If this is not true, the next call would start jsd...
-        rv = jsds->OnForRuntime(cx->runtime);
-        jsds_IsOn = NS_SUCCEEDED(rv);
-      }
     }
 
     // If there is a debug handler registered for this runtime AND
     // ((jsd is on AND has a hook) OR (jsd isn't on (something else debugs)))
     // then something useful will be done with our request to debug.
     debugPossible = ((jsds_IsOn && (jsdHook != nsnull)) || !jsds_IsOn);
   }
 #endif
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -67,22 +67,23 @@ interface jsdIExecutionHook;
 interface jsdICallHook;
 interface jsdIEphemeral;
 interface jsdIContext;
 interface jsdIStackFrame;
 interface jsdIScript;
 interface jsdIValue;
 interface jsdIObject;
 interface jsdIProperty;
+interface jsdIActivationCallback;
 
 /**
  * Debugger service.  It's not a good idea to have more than one active client of
  * the debugger service.
  */
-[scriptable, uuid(dc0a24db-f8ac-4889-80d0-6016545a2dda)]
+[scriptable, uuid(01769775-c77c-47f9-8848-0abbab404215)]
 interface jsdIDebuggerService : nsISupports
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext        JSDContext;
 
     /**
      * Called when an error or warning occurs.
      */
@@ -211,29 +212,44 @@ interface jsdIDebuggerService : nsISuppo
     
     /**
      * |true| if the debugger service has been turned on.  This does not
      * necessarily mean another app is actively using the service, as the 
      * autostart pref may have turned the service on.
      */
     readonly attribute boolean isOn;
 
+
+    /**
+     * Synchronous activation of the debugger is no longer supported,
+     * and will throw an exception.
+     */
+    void on ();
+
     /**
      * Turn on the debugger.  This function should only be called from JavaScript
      * code.  The debugger will be enabled on the runtime the call is made on,
      * as determined by nsIXPCNativeCallContext.
+     *
+     * The debugger will be activated asynchronously, because there can be no JS
+     * on the stack when code is to be re-compiled for debug mode.
      */
-    void on ();
+    void asyncOn (in jsdIActivationCallback callback);
+    
     /**
-     * Turn on the debugger for a given runtime.
-     *
-     * @param rt The runtime you want to debug.  You cannot turn the debugger
-     *           on for multiple runtimes.
+     * Called by nsIXPConnect after it's had a chance to recompile for
+     * debug mode.
      */
-    [noscript] void onForRuntime (in JSRuntime rt);
+    [noscript] void activateDebugger(in JSRuntime rt);
+
+    /**
+     * Recompile all active scripts in the runtime for debugMode.
+     */
+    [noscript] void recompileForDebugMode(in JSRuntime rt, in PRBool mode);
+
     /**
      * Turn the debugger off.  This will invalidate all of your jsdIEphemeral
      * derived objects, and clear all of your breakpoints.  In theory you
      * should be able to turn the debugger back on at some later time without
      * any problems.
      */
     void off ();
 
@@ -453,16 +469,25 @@ interface jsdIFilter : nsISupports
      * Line number for the end of this filter.  Line numbers are one based.
      * Assigning a 0 to this attribute will tell the debugger to ignore from
      * |startLine| to the end of the file.
      */
     attribute unsigned long endLine;
 };
 
 /**
+ * Notify client code that debugMode has been activated.
+ */
+[scriptable, uuid(6da7f5fb-3a84-4abe-9e23-8b2045960732)]
+interface jsdIActivationCallback : nsISupports
+{
+    void onDebuggerActivated ();
+};
+
+/**
  * Pass an instance of one of these to jsdIDebuggerService::enterNestedEventLoop.
  */
 [scriptable, uuid(88bea60f-9b5d-4b39-b08b-1c3a278782c6)]
 interface jsdINestCallback : nsISupports
 {
     /**
      * This method will be called after pre-nesting work has completed, such
      * as pushing the js context and network event queue, but before the new
--- a/js/jsd/jsd_scpt.c
+++ b/js/jsd/jsd_scpt.c
@@ -580,21 +580,16 @@ jsd_NewScriptHookProc(
     JSD_ScriptHookProc      hook;
     void*                   hookData;
     
     JSD_ASSERT_VALID_CONTEXT(jsdc);
 
     if( JSD_IS_DANGEROUS_THREAD(jsdc) )
         return;
     
-#ifdef LIVEWIRE
-    if( 1 == lineno )
-        jsdlw_PreLoadSource(jsdc, LWDBG_GetCurrentApp(), filename, JS_TRUE );
-#endif
-    
     JSD_LOCK_SCRIPTS(jsdc);
     jsdscript = _newJSDScript(jsdc, cx, script, fun);
     JSD_UNLOCK_SCRIPTS(jsdc);
     if( ! jsdscript )
         return;
 
 #ifdef JSD_DUMP
     JSD_LOCK_SCRIPTS(jsdc);
@@ -606,17 +601,17 @@ jsd_NewScriptHookProc(
     /* local in case jsdc->scriptHook gets cleared on another thread */
     JSD_LOCK();
     hook = jsdc->scriptHook;
     hookData = jsdc->scriptHookData;
     JSD_UNLOCK();
 
     if( hook )
         hook(jsdc, jsdscript, JS_TRUE, hookData);
-}                
+}
 
 void
 jsd_DestroyScriptHookProc( 
                 JSContext   *cx,
                 JSScript    *script,
                 void*       callerdata )
 {
     JSDScript* jsdscript = NULL;
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -2392,41 +2392,61 @@ jsdService::GetIsOn (PRBool *_rval)
 {
     *_rval = mOn;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 jsdService::On (void)
 {
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+jsdService::AsyncOn (jsdIActivationCallback *activationCallback)
+{
     nsresult  rv;
 
     /* get JS things from the CallContext */
     nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
     if (NS_FAILED(rv)) return rv;
 
     nsAXPCNativeCallContext *cc = nsnull;
     rv = xpc->GetCurrentNativeCallContext(&cc);
     if (NS_FAILED(rv)) return rv;
 
     JSContext *cx;
     rv = cc->GetJSContext (&cx);
     if (NS_FAILED(rv)) return rv;
+
+    mActivationCallback = activationCallback;
     
-    return OnForRuntime(JS_GetRuntime (cx));
-    
+    return xpc->SetDebugModeWhenPossible(PR_TRUE);
 }
 
 NS_IMETHODIMP
-jsdService::OnForRuntime (JSRuntime *rt)
+jsdService::RecompileForDebugMode (JSRuntime *rt, JSBool mode) {
+  JSContext *cx;
+  JSContext *iter = NULL;
+
+  while ((cx = JS_ContextIterator (rt, &iter))) {
+      JS_SetDebugMode(cx, mode);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+jsdService::ActivateDebugger (JSRuntime *rt)
 {
     if (mOn)
         return (rt == mRuntime) ? NS_OK : NS_ERROR_ALREADY_INITIALIZED;
 
     mRuntime = rt;
+    RecompileForDebugMode(rt, JS_TRUE);
 
     if (gLastGCProc == jsds_GCCallbackProc)
         /* condition indicates that the callback proc has not been set yet */
         gLastGCProc = JS_SetGCCallbackRT (rt, jsds_GCCallbackProc);
 
     mCx = JSD_DebuggerOnForUser (rt, NULL, NULL);
     if (!mCx)
         return NS_ERROR_FAILURE;
@@ -2466,16 +2486,20 @@ jsdService::OnForRuntime (JSRuntime *rt)
         JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL);
     else
         JSD_ClearFunctionHook (mCx);
     mOn = PR_TRUE;
 
 #ifdef DEBUG
     printf ("+++ JavaScript debugging hooks installed.\n");
 #endif
+
+    if (mActivationCallback)
+        return mActivationCallback->OnDebuggerActivated();
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 jsdService::Off (void)
 {
     if (!mOn)
         return NS_OK;
@@ -2516,16 +2540,23 @@ jsdService::Off (void)
     mCx = nsnull;
     mRuntime = nsnull;
     mOn = PR_FALSE;
 
 #ifdef DEBUG
     printf ("+++ JavaScript debugging hooks removed.\n");
 #endif
 
+    nsresult rv;
+    nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
+    if (NS_FAILED(rv))
+        return rv;
+
+    xpc->SetDebugModeWhenPossible(PR_FALSE);
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 jsdService::GetPauseDepth(PRUint32 *_rval)
 {
     NS_ENSURE_ARG_POINTER(_rval);
     *_rval = mPauseLevel;
@@ -2964,17 +2995,17 @@ jsdService::ExitNestedEventLoop (PRUint3
 /* hook attribute get/set functions */
 
 NS_IMETHODIMP
 jsdService::SetErrorHook (jsdIErrorHook *aHook)
 {
     mErrorHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetErrorReporter (mCx, jsds_ErrorHookProc, NULL);
     else
         JSD_SetErrorReporter (mCx, NULL, NULL);
@@ -3008,17 +3039,17 @@ jsdService::GetBreakpointHook (jsdIExecu
 }
 
 NS_IMETHODIMP
 jsdService::SetDebugHook (jsdIExecutionHook *aHook)
 {    
     mDebugHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL);
     else
         JSD_ClearDebugBreakHook (mCx);
@@ -3036,17 +3067,17 @@ jsdService::GetDebugHook (jsdIExecutionH
 }
 
 NS_IMETHODIMP
 jsdService::SetDebuggerHook (jsdIExecutionHook *aHook)
 {    
     mDebuggerHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL);
     else
         JSD_ClearDebuggerHook (mCx);
@@ -3064,17 +3095,17 @@ jsdService::GetDebuggerHook (jsdIExecuti
 }
 
 NS_IMETHODIMP
 jsdService::SetInterruptHook (jsdIExecutionHook *aHook)
 {    
     mInterruptHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL);
     else
         JSD_ClearInterruptHook (mCx);
@@ -3092,17 +3123,17 @@ jsdService::GetInterruptHook (jsdIExecut
 }
 
 NS_IMETHODIMP
 jsdService::SetScriptHook (jsdIScriptHook *aHook)
 {    
     mScriptHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
     
     if (aHook)
         JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL);
     /* we can't unset it if !aHook, because we still need to see script
      * deletes in order to Release the jsdIScripts held in JSDScript
@@ -3120,17 +3151,17 @@ jsdService::GetScriptHook (jsdIScriptHoo
 }
 
 NS_IMETHODIMP
 jsdService::SetThrowHook (jsdIExecutionHook *aHook)
 {    
     mThrowHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL);
     else
         JSD_ClearThrowHook (mCx);
@@ -3148,17 +3179,17 @@ jsdService::GetThrowHook (jsdIExecutionH
 }
 
 NS_IMETHODIMP
 jsdService::SetTopLevelHook (jsdICallHook *aHook)
 {    
     mTopLevelHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL);
     else
         JSD_ClearTopLevelHook (mCx);
@@ -3176,17 +3207,17 @@ jsdService::GetTopLevelHook (jsdICallHoo
 }
 
 NS_IMETHODIMP
 jsdService::SetFunctionHook (jsdICallHook *aHook)
 {    
     mFunctionHook = aHook;
 
     /* if the debugger isn't initialized, that's all we can do for now.  The
-     * OnForRuntime() method will do the rest when the coast is clear.
+     * ActivateDebugger() method will do the rest when the coast is clear.
      */
     if (!mCx || mPauseLevel)
         return NS_OK;
 
     if (aHook)
         JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL);
     else
         JSD_ClearFunctionHook (mCx);
@@ -3269,17 +3300,17 @@ jsdASObserver::Observe (nsISupports *aSu
     if (NS_FAILED(rv))
         return rv;    
 
     JSRuntime *rt;
     rts->GetRuntime (&rt);
     if (NS_FAILED(rv))
         return rv;
 
-    rv = jsds->OnForRuntime(rt);
+    rv = jsds->ActivateDebugger(rt);
     if (NS_FAILED(rv))
         return rv;
     
     return NS_OK;
 }
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(jsdASObserver)
 NS_DEFINE_NAMED_CID(JSDSERVICE_CID);
--- a/js/jsd/jsd_xpc.h
+++ b/js/jsd/jsd_xpc.h
@@ -300,17 +300,17 @@ class jsdService : public jsdIDebuggerSe
     nsCOMPtr<jsdIExecutionHook> mBreakpointHook;
     nsCOMPtr<jsdIExecutionHook> mDebugHook;
     nsCOMPtr<jsdIExecutionHook> mDebuggerHook;
     nsCOMPtr<jsdIExecutionHook> mInterruptHook;
     nsCOMPtr<jsdIScriptHook>    mScriptHook;
     nsCOMPtr<jsdIExecutionHook> mThrowHook;
     nsCOMPtr<jsdICallHook>      mTopLevelHook;
     nsCOMPtr<jsdICallHook>      mFunctionHook;
-
+    nsCOMPtr<jsdIActivationCallback> mActivationCallback;
 };
 
 #endif /* JSDSERVICE_H___ */
 
 
 /* graveyard */
 
 #if 0
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -648,16 +648,19 @@ JSRuntime::init(uint32 maxbytes)
         return false;
     stateChange = JS_NEW_CONDVAR(gcLock);
     if (!stateChange)
         return false;
     debuggerLock = JS_NEW_LOCK();
     if (!debuggerLock)
         return false;
 #endif
+
+    debugMode = JS_FALSE;
+
     return propertyTree.init() && js_InitThreads(this);
 }
 
 JSRuntime::~JSRuntime()
 {
 #ifdef DEBUG
     /* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
     if (!JS_CLIST_IS_EMPTY(&contextList)) {
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1411,16 +1411,21 @@ struct JSRuntime {
     JSString            *emptyString;
 
     /* List of active contexts sharing this runtime; protected by gcLock. */
     JSCList             contextList;
 
     /* Per runtime debug hooks -- see jsprvtd.h and jsdbgapi.h. */
     JSDebugHooks        globalDebugHooks;
 
+    /*
+     * Right now, we only support runtime-wide debugging.
+     */
+    JSBool              debugMode;
+
 #ifdef JS_TRACER
     /* True if any debug hooks not supported by the JIT are enabled. */
     bool debuggerInhibitsJIT() const {
         return (globalDebugHooks.interruptHook ||
                 globalDebugHooks.callHook);
     }
 #endif
 
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -48,17 +48,17 @@
 #include "methodjit/MonoIC.h"
 
 #include "jsgcinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 JSCompartment::JSCompartment(JSRuntime *rt)
-  : rt(rt), principals(NULL), data(NULL), marked(false), debugMode(false),
+  : rt(rt), principals(NULL), data(NULL), marked(false), debugMode(rt->debugMode),
     anynameObject(NULL), functionNamespaceObject(NULL)
 {
     JS_INIT_CLIST(&scripts);
 }
 
 JSCompartment::~JSCompartment()
 {
 }
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -103,16 +103,22 @@ IsScriptLive(JSContext *cx, JSScript *sc
     for (AllFramesIter i(cx); !i.done(); ++i) {
         if (i.fp()->maybeScript() == script)
             return true;
     }
     return false;
 }
 #endif
 
+JS_PUBLIC_API(void)
+JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug)
+{
+    rt->debugMode = debug;
+}
+
 JS_FRIEND_API(JSBool)
 js_SetDebugMode(JSContext *cx, JSBool debug)
 {
     cx->compartment->debugMode = debug;
 #ifdef JS_METHODJIT
     for (JSScript *script = (JSScript *)cx->compartment->scripts.next;
          &script->links != &cx->compartment->scripts;
          script = (JSScript *)script->links.next) {
--- a/js/src/jsdbgapi.h
+++ b/js/src/jsdbgapi.h
@@ -45,16 +45,23 @@
  */
 #include "jsapi.h"
 #include "jsopcode.h"
 #include "jsprvtd.h"
 
 JS_BEGIN_EXTERN_C
 
 /*
+ * Currently, we only support runtime-wide debugging. In the future, we should
+ * be able to support compartment-wide debugging.
+ */
+extern JS_PUBLIC_API(void)
+JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug);
+
+/*
  * Debug mode is a compartment-wide mode that enables a debugger to attach
  * to and interact with running methodjit-ed frames. In particular, it causes
  * every function to be compiled as if an eval was present (so eval-in-frame)
  * can work, and it ensures that functions can be re-JITed for other debug
  * features. In general, it is not safe to interact with frames that were live
  * before debug mode was enabled. For this reason, it is also not safe to
  * enable debug mode while frames are live.
  */
--- a/js/src/xpconnect/idl/nsIXPConnect.idl
+++ b/js/src/xpconnect/idl/nsIXPConnect.idl
@@ -394,17 +394,17 @@ interface nsIXPCFunctionThisTranslator :
 %{ C++
 // For use with the service manager
 // {CB6593E0-F9B2-11d2-BDD6-000064657374}
 #define NS_XPCONNECT_CID \
 { 0xcb6593e0, 0xf9b2, 0x11d2, \
     { 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
 %}
 
-[uuid(fb780ace-dced-432b-bb82-8df7d4f919c8)]
+[uuid(c825b64b-d537-4e53-822e-547049aae9c9)]
 interface nsIXPConnect : nsISupports
 {
 %{ C++
   NS_DEFINE_STATIC_CID_ACCESSOR(NS_XPCONNECT_CID)
 %}
 
     /**
      * Initializes classes on a global object that has already been created.
@@ -822,9 +822,18 @@ interface nsIXPConnect : nsISupports
     nsIXPConnectJSObjectHolder holdObject(in JSContextPtr aJSContext,
                                           in JSObjectPtr aObject);
 
     /**
      * Return the caller object of the current call from JS.
      */
     [noscript,notxpcom] void getCaller(out JSContextPtr aJSContext,
                                        out JSObjectPtr aObject);
+
+    /**
+     * When we place the browser in JS debug mode, there can't be any
+     * JS on the stack. This is because we currently activate debugMode 
+     * on all scripts in the JSRuntime when the debugger is activated.
+     * This method will turn debug mode on or off when the context 
+     * stack reaches zero length.
+     */
+    [noscript] void setDebugModeWhenPossible(in PRBool mode);
 };
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -56,27 +56,31 @@
 #include "nsIURI.h"
 
 #include "jstypedarray.h"
 
 #include "XrayWrapper.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 
+#include "jsdIDebuggerService.h"
+
 NS_IMPL_THREADSAFE_ISUPPORTS6(nsXPConnect,
                               nsIXPConnect,
                               nsISupportsWeakReference,
                               nsIThreadObserver,
                               nsIJSRuntimeService,
                               nsIJSContextStack,
                               nsIThreadJSContextStack)
 
 nsXPConnect* nsXPConnect::gSelf = nsnull;
 JSBool       nsXPConnect::gOnceAliveNowDead = JS_FALSE;
 PRUint32     nsXPConnect::gReportAllJSExceptions = 0;
+JSBool       nsXPConnect::gDebugMode = JS_FALSE;
+JSBool       nsXPConnect::gDesiredDebugMode = JS_FALSE;
 
 // Global cache of the default script security manager (QI'd to
 // nsIScriptSecurityManager)
 nsIScriptSecurityManager *nsXPConnect::gScriptSecurityManager = nsnull;
 
 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";
@@ -2402,16 +2406,40 @@ nsXPConnect::Peek(JSContext * *_retval)
     {
         *_retval = nsnull;
         return NS_ERROR_FAILURE;
     }
 
     return data->GetJSContextStack()->Peek(_retval);
 }
 
+void 
+nsXPConnect::CheckForDebugMode(JSRuntime *rt) {
+    if (gDebugMode != gDesiredDebugMode) {
+        nsresult rv;
+        const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
+        nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
+        if (NS_SUCCEEDED(rv)) {
+            if (gDesiredDebugMode == PR_FALSE) {
+                rv = jsds->RecompileForDebugMode(rt, PR_FALSE);
+            } else {
+                rv = jsds->ActivateDebugger(rt);
+            }
+        }
+
+        if (NS_SUCCEEDED(rv)) {
+            JS_SetRuntimeDebugMode(rt, gDesiredDebugMode);
+            gDebugMode = gDesiredDebugMode;
+        } else {
+            // if the attempt failed, cancel the debugMode request
+            gDesiredDebugMode = gDebugMode;
+        }
+    }
+}
+
 /* JSContext Pop (); */
 NS_IMETHODIMP
 nsXPConnect::Pop(JSContext * *_retval)
 {
     XPCPerThreadData* data = XPCPerThreadData::GetData(nsnull);
 
     if(!data)
     {
@@ -2427,16 +2455,25 @@ nsXPConnect::Pop(JSContext * *_retval)
 NS_IMETHODIMP
 nsXPConnect::Push(JSContext * cx)
 {
     XPCPerThreadData* data = XPCPerThreadData::GetData(cx);
 
     if(!data)
         return NS_ERROR_FAILURE;
 
+    PRInt32 count;
+    nsresult rv;
+    rv = data->GetJSContextStack()->GetCount(&count);
+    if (NS_FAILED(rv))
+        return rv;
+
+    if (count == 0)
+        CheckForDebugMode(mRuntime->GetJSRuntime());
+
     return data->GetJSContextStack()->Push(cx);
 }
 
 /* attribute JSContext SafeJSContext; */
 NS_IMETHODIMP
 nsXPConnect::GetSafeJSContext(JSContext * *aSafeJSContext)
 {
     NS_ASSERTION(aSafeJSContext, "loser!");
@@ -2534,16 +2571,23 @@ nsXPConnect::GetCaller(JSContext **aJSCo
 {
     XPCCallContext *ccx = XPCPerThreadData::GetData(nsnull)->GetCallContext();
     *aJSContext = ccx->GetJSContext();
 
     // Set to the caller in XPC_WN_Helper_{Call,Construct}
     *aObj = ccx->GetFlattenedJSObject();
 }
 
+NS_IMETHODIMP
+nsXPConnect::SetDebugModeWhenPossible(PRBool mode)
+{
+    gDesiredDebugMode = mode;
+    return NS_OK;
+}
+
 /* These are here to be callable from a debugger */
 JS_BEGIN_EXTERN_C
 JS_EXPORT_API(void) DumpJSStack()
 {
     nsresult rv;
     nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
     if(NS_SUCCEEDED(rv) && xpc)
         xpc->DebugDumpJSStack(PR_TRUE, PR_TRUE, PR_FALSE);
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -621,16 +621,19 @@ private:
 
 #ifndef XPCONNECT_STANDALONE
     typedef nsBaseHashtable<nsVoidPtrHashKey, nsISupports*, nsISupports*> ScopeSet;
     ScopeSet mScopes;
 #endif
     nsCOMPtr<nsIXPCScriptable> mBackstagePass;
 
     static PRUint32 gReportAllJSExceptions;
+    static JSBool gDebugMode;
+    static JSBool gDesiredDebugMode;
+    static inline void CheckForDebugMode(JSRuntime *rt);
 
 public:
     static nsIScriptSecurityManager *gScriptSecurityManager;
 };
 
 /***************************************************************************/
 
 class XPCRootSetElem