Bug 699528. Make pausing/unpausing JSD try to turn off/on debug mode as needed. r=sfink a=LegNeato
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 09 Nov 2011 16:11:39 -0500
changeset 79232 d706932d86604397dd0447a4827578300d7faa8a
parent 79231 3d16e65f94818dbd64e0febd83abeacfe7b7d2bf
child 79233 c2e99970507689dac395b98c9eac48c80664d6d9
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink, LegNeato
bugs699528
milestone9.0
Bug 699528. Make pausing/unpausing JSD try to turn off/on debug mode as needed. r=sfink a=LegNeato The new boolean argument to SetDebugModeWhenPossible is needed because if we allow sync-disable of debug mode we seem to crash when loading pages with Firebug active.
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd_xpc.cpp
js/jsd/jsd_xpc.h
js/src/xpconnect/idl/nsIXPConnect.idl
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcprivate.h
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -269,17 +269,20 @@ interface jsdIDebuggerService : nsISuppo
      * depth", and equal number of unPause calles must be made to resume
      * normal debugging.
      *
      * @return depth Number of times pause has been called since the debugger
      *               has been unpaused.
      */
     unsigned long pause();
     /**
-     * Undo a pause.
+     * Undo a pause.  Once this is called, the debugger won't start
+     * getting execution callbacks until the stack is fully unwound so
+     * that no JS scripts are live.  There is no way to query whether
+     * there are such scripts left to unwind at a given point in time.
      *
      * @return depth The number of remaining pending pause calls.
      */
     unsigned long unPause();
     
     /**
      * Force the engine to perform garbage collection.
      */
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -466,17 +466,17 @@ jsds_NotifyPendingDeadScripts (JSContext
     JSRuntime *rt = JS_GetRuntime(cx);
 #endif
     jsdService *jsds = gJsds;
 
     nsCOMPtr<jsdIScriptHook> hook;
     if (jsds) {
         NS_ADDREF(jsds);
         jsds->GetScriptHook (getter_AddRefs(hook));
-        jsds->Pause(nsnull);
+        jsds->DoPause(nsnull, true);
     }
 
     DeadScript *deadScripts = gDeadScripts;
     gDeadScripts = nsnull;
     while (deadScripts) {
         DeadScript *ds = deadScripts;
         /* get next deleted script */
         deadScripts = reinterpret_cast<DeadScript *>
@@ -501,17 +501,17 @@ jsds_NotifyPendingDeadScripts (JSContext
 
         /* addref came from the FromPtr call in jsds_ScriptHookProc */
         NS_RELEASE(ds->script);
         /* free the struct! */
         PR_Free(ds);
     }
 
     if (jsds) {
-        jsds->UnPause(nsnull);
+        jsds->DoUnPause(nsnull, true);
         NS_RELEASE(jsds);
     }
 }
 
 static JSBool
 jsds_GCCallbackProc (JSContext *cx, JSGCStatus status)
 {
 #ifdef DEBUG_verbose
@@ -578,19 +578,19 @@ jsds_ErrorHookProc (JSDContext *jsdc, JS
     else
     {
         line     = 0;
         pos      = 0;
         flags    = 0;
         errnum   = 0;
     }
     
-    gJsds->Pause(nsnull);
+    gJsds->DoPause(nsnull, true);
     hook->OnError (nsDependentCString(message), fileName, line, pos, flags, errnum, val, &rval);
-    gJsds->UnPause(nsnull);
+    gJsds->DoUnPause(nsnull, true);
     
     running = PR_FALSE;
     if (!rval)
         return JSD_ERROR_REPORTER_DEBUG;
     
     return JSD_ERROR_REPORTER_PASS_ALONG;
 }
 
@@ -621,19 +621,19 @@ jsds_CallHookProc (JSDContext* jsdc, JSD
 
     if (!jsds_FilterHook (jsdc, jsdthreadstate))
         return JS_FALSE;
 
     JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate);
     nsCOMPtr<jsdIStackFrame> frame =
         getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate,
                                               native_frame));
-    gJsds->Pause(nsnull);
+    gJsds->DoPause(nsnull, true);
     hook->OnCall(frame, type);    
-    gJsds->UnPause(nsnull);
+    gJsds->DoUnPause(nsnull, true);
     jsdStackFrame::InvalidateAll();
 
     return JS_TRUE;
 }
 
 static PRUint32
 jsds_ExecutionHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate,
                         uintN type, void* callerdata, jsval* rval)
@@ -683,23 +683,23 @@ jsds_ExecutionHookProc (JSDContext* jsdc
     
     if (!jsds_FilterHook (jsdc, jsdthreadstate))
         return JSD_HOOK_RETURN_CONTINUE;
     
     JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate);
     nsCOMPtr<jsdIStackFrame> frame =
         getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate,
                                               native_frame));
-    gJsds->Pause(nsnull);
+    gJsds->DoPause(nsnull, true);
     jsdIValue *inout_rv = js_rv;
     NS_IF_ADDREF(inout_rv);
     hook->OnExecute (frame, type, &inout_rv, &hook_rv);
     js_rv = inout_rv;
     NS_IF_RELEASE(inout_rv);
-    gJsds->UnPause(nsnull);
+    gJsds->DoUnPause(nsnull, true);
     jsdStackFrame::InvalidateAll();
         
     if (hook_rv == JSD_HOOK_RETURN_RET_WITH_VAL ||
         hook_rv == JSD_HOOK_RETURN_THROW_WITH_VAL) {
         *rval = JSVAL_VOID;
         if (js_rv) {
             JSDValue *jsdv;
             if (NS_SUCCEEDED(js_rv->GetJSDValue (&jsdv)))
@@ -729,19 +729,19 @@ jsds_ScriptHookProc (JSDContext* jsdc, J
             return;
         }
             
         nsCOMPtr<jsdIScript> script = 
             getter_AddRefs(jsdScript::FromPtr(jsdc, jsdscript));
 #ifdef CAUTIOUS_SCRIPTHOOK
         JS_UNKEEP_ATOMS(rt);
 #endif
-        gJsds->Pause(nsnull);
+        gJsds->DoPause(nsnull, true);
         hook->OnScriptCreated (script);
-        gJsds->UnPause(nsnull);
+        gJsds->DoUnPause(nsnull, true);
 #ifdef CAUTIOUS_SCRIPTHOOK
         JS_KEEP_ATOMS(rt);
 #endif
     } else {
         /* a script is being destroyed.  even if there is no registered hook
          * we'll still need to invalidate the jsdIScript record, in order
          * to remove the reference held in the JSDScript private data. */
         nsCOMPtr<jsdIScript> jsdis = 
@@ -758,19 +758,19 @@ jsds_ScriptHookProc (JSDContext* jsdc, J
                 return;
 
             /* if GC *isn't* running, we can tell the user about the script
              * delete now. */
 #ifdef CAUTIOUS_SCRIPTHOOK
             JS_UNKEEP_ATOMS(rt);
 #endif
                 
-            gJsds->Pause(nsnull);
+            gJsds->DoPause(nsnull, true);
             hook->OnScriptDestroyed (jsdis);
-            gJsds->UnPause(nsnull);
+            gJsds->DoUnPause(nsnull, true);
 #ifdef CAUTIOUS_SCRIPTHOOK
             JS_KEEP_ATOMS(rt);
 #endif
         } else {
             /* if a GC *is* running, we've got to wait until it's done before
              * we can execute any JS, so we queue the notification in a PRCList
              * until GC tells us it's done. See jsds_GCCallbackProc(). */
             DeadScript *ds = PR_NEW(DeadScript);
@@ -2541,31 +2541,23 @@ 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);
+    nsCOMPtr<nsIXPConnect_MOZILLA_10_BRANCH> xpc =
+        do_GetService(nsIXPConnect::GetCID(), &rv);
     if (NS_FAILED(rv)) return rv;
 
     mActivationCallback = activationCallback;
     
-    return xpc->SetDebugModeWhenPossible(PR_TRUE);
+    return xpc->SetDebugModeWhenPossible(PR_TRUE, PR_TRUE);
 }
 
 NS_IMETHODIMP
 jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, PRBool mode) {
   NS_ASSERTION(NS_IsMainThread(), "wrong thread");
   /* XPConnect now does this work itself, so this IDL entry point is no longer used. */
   return NS_ERROR_NOT_IMPLEMENTED;
 }
@@ -2697,59 +2689,82 @@ jsdService::Off (void)
 
     DeactivateDebugger();
 
 #ifdef DEBUG
     printf ("+++ JavaScript debugging hooks removed.\n");
 #endif
 
     nsresult rv;
-    nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
+    nsCOMPtr<nsIXPConnect_MOZILLA_10_BRANCH> xpc =
+        do_GetService(nsIXPConnect::GetCID(), &rv);
     if (NS_FAILED(rv))
         return rv;
 
-    xpc->SetDebugModeWhenPossible(PR_FALSE);
+    xpc->SetDebugModeWhenPossible(PR_FALSE, PR_TRUE);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 jsdService::GetPauseDepth(PRUint32 *_rval)
 {
     NS_ENSURE_ARG_POINTER(_rval);
     *_rval = mPauseLevel;
     return NS_OK;
 }
     
 NS_IMETHODIMP
 jsdService::Pause(PRUint32 *_rval)
 {
+    return DoPause(_rval, false);
+}
+
+nsresult
+jsdService::DoPause(PRUint32 *_rval, bool internalCall)
+{
     if (!mCx)
         return NS_ERROR_NOT_INITIALIZED;
 
     if (++mPauseLevel == 1) {
         JSD_SetErrorReporter (mCx, NULL, NULL);
         JSD_ClearThrowHook (mCx);
         JSD_ClearInterruptHook (mCx);
         JSD_ClearDebuggerHook (mCx);
         JSD_ClearDebugBreakHook (mCx);
         JSD_ClearTopLevelHook (mCx);
         JSD_ClearFunctionHook (mCx);
         JSD_DebuggerPause (mCx);
+
+        nsresult rv;
+        nsCOMPtr<nsIXPConnect_MOZILLA_10_BRANCH> xpc =
+            do_GetService(nsIXPConnect::GetCID(), &rv);
+        if (NS_FAILED(rv)) return rv;
+
+        if (!internalCall) {
+            rv = xpc->SetDebugModeWhenPossible(PR_FALSE, PR_FALSE);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
     }
 
     if (_rval)
         *_rval = mPauseLevel;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 jsdService::UnPause(PRUint32 *_rval)
 {
+    return DoUnPause(_rval, false);
+}
+
+nsresult
+jsdService::DoUnPause(PRUint32 *_rval, bool internalCall)
+{
     if (!mCx)
         return NS_ERROR_NOT_INITIALIZED;
 
     if (mPauseLevel == 0)
         return NS_ERROR_NOT_AVAILABLE;
 
     /* check mOn before we muck with this stuff, it's possible the debugger
      * was turned off while we were paused.
@@ -2769,16 +2784,26 @@ jsdService::UnPause(PRUint32 *_rval)
         if (mTopLevelHook)
             JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL);
         else
             JSD_ClearTopLevelHook (mCx);
         if (mFunctionHook)
             JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL);
         else
             JSD_ClearFunctionHook (mCx);
+
+        nsresult rv;
+        nsCOMPtr<nsIXPConnect_MOZILLA_10_BRANCH> xpc =
+            do_GetService(nsIXPConnect::GetCID(), &rv);
+        if (NS_FAILED(rv)) return rv;
+
+        if (!internalCall) {
+            rv = xpc->SetDebugModeWhenPossible(PR_TRUE, PR_FALSE);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
     }
     
     if (_rval)
         *_rval = mPauseLevel;
 
     return NS_OK;
 }
 
@@ -3105,19 +3130,19 @@ jsdService::EnterNestedEventLoop (jsdINe
     if (NS_FAILED(rv))
         return rv;
     PRUint32 nestLevel = ++mNestedLoopLevel;
     
     nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
 
     if (NS_SUCCEEDED(stack->Push(nsnull))) {
         if (callback) {
-            Pause(nsnull);
+            DoPause(nsnull, true);
             rv = callback->OnNest();
-            UnPause(nsnull);
+            DoUnPause(nsnull, true);
         }
         
         while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) {
             if (!NS_ProcessNextEvent(thread))
                 rv = NS_ERROR_UNEXPECTED;
         }
 
         JSContext* cx;
--- a/js/jsd/jsd_xpc.h
+++ b/js/jsd/jsd_xpc.h
@@ -286,16 +286,19 @@ class jsdService : public jsdIDebuggerSe
     }
 
     virtual ~jsdService();
     
     static jsdService *GetService ();
 
     PRBool CheckInterruptHook() { return !!mInterruptHook; }
     
+    nsresult DoPause(PRUint32 *_rval, bool internalCall);
+    nsresult DoUnPause(PRUint32 *_rval, bool internalCall);
+
   private:
     PRBool      mOn;
     PRUint32    mPauseLevel;
     PRUint32    mNestedLoopLevel;
     JSDContext *mCx;
     JSRuntime  *mRuntime;
 
     nsCOMPtr<jsdIErrorHook>     mErrorHook;
--- a/js/src/xpconnect/idl/nsIXPConnect.idl
+++ b/js/src/xpconnect/idl/nsIXPConnect.idl
@@ -792,8 +792,17 @@ interface nsIXPConnect : nsISupports
      * 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 boolean mode);
 };
+
+[uuid(44bf9047-914a-4f47-a1ec-e417927bacd1)]
+interface nsIXPConnect_MOZILLA_10_BRANCH : nsISupports
+{
+    // A 2-argument form of setDebugModeWhenPossible that allows
+    // controlling whether sync disable of debug mode is allowed.
+    [noscript] void setDebugModeWhenPossible(in boolean mode,
+                                             in boolean allowSyncDisable);
+};
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -62,24 +62,25 @@
 #include "XrayWrapper.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 
 #include "jsdIDebuggerService.h"
 
 #include "xpcquickstubs.h"
 
-NS_IMPL_THREADSAFE_ISUPPORTS7(nsXPConnect,
+NS_IMPL_THREADSAFE_ISUPPORTS8(nsXPConnect,
                               nsIXPConnect,
                               nsISupportsWeakReference,
                               nsIThreadObserver,
                               nsIJSRuntimeService,
                               nsIJSContextStack,
                               nsIThreadJSContextStack,
-                              nsIJSEngineTelemetryStats)
+                              nsIJSEngineTelemetryStats,
+                              nsIXPConnect_MOZILLA_10_BRANCH)
 
 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
@@ -2880,18 +2881,24 @@ nsXPConnect::Base64Decode(JSContext *cx,
 
     *out = STRING_TO_JSVAL(str);
     return JS_TRUE;
 }
 
 NS_IMETHODIMP
 nsXPConnect::SetDebugModeWhenPossible(PRBool mode)
 {
+    return SetDebugModeWhenPossible(mode, PR_FALSE);
+}
+
+NS_IMETHODIMP
+nsXPConnect::SetDebugModeWhenPossible(PRBool mode, PRBool allowSyncDisable)
+{
     gDesiredDebugMode = mode;
-    if (!mode)
+    if (!mode && allowSyncDisable)
         CheckForDebugMode(mRuntime->GetJSRuntime());
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPConnect::GetTelemetryValue(JSContext *cx, jsval *rval)
 {
     JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -457,27 +457,29 @@ const PRBool OBJ_IS_NOT_GLOBAL = PR_FALS
 
 class nsXPConnect : public nsIXPConnect,
                     public nsIThreadObserver,
                     public nsSupportsWeakReference,
                     public nsCycleCollectionJSRuntime,
                     public nsCycleCollectionParticipant,
                     public nsIJSRuntimeService,
                     public nsIThreadJSContextStack,
-                    public nsIJSEngineTelemetryStats
+                    public nsIJSEngineTelemetryStats,
+                    public nsIXPConnect_MOZILLA_10_BRANCH
 {
 public:
     // all the interface method declarations...
     NS_DECL_ISUPPORTS
     NS_DECL_NSIXPCONNECT
     NS_DECL_NSITHREADOBSERVER
     NS_DECL_NSIJSRUNTIMESERVICE
     NS_DECL_NSIJSCONTEXTSTACK
     NS_DECL_NSITHREADJSCONTEXTSTACK
     NS_DECL_NSIJSENGINETELEMETRYSTATS
+    NS_DECL_NSIXPCONNECT_MOZILLA_10_BRANCH
 
     // non-interface implementation
 public:
     // These get non-addref'd pointers
     static nsXPConnect*  GetXPConnect();
     static nsXPConnect*  FastGetXPConnect() { return gSelf ? gSelf : GetXPConnect(); }
     static XPCJSRuntime* GetRuntimeInstance();
     XPCJSRuntime* GetRuntime() {return mRuntime;}