From 46c277ecd7f194188a6b4ace23d7fe01d56f0bb9 Mon Sep 17 00:00:00 2001 From: Robert Sayre Date: Fri, 29 Oct 2010 18:35:07 -0400 Subject: [PATCH] Bug 595243 - Expose debugMode to JSD. r=gal --- dom/base/nsJSEnvironment.cpp | 4 -- js/jsd/idl/jsdIDebuggerService.idl | 41 ++++++++++++++++---- js/jsd/jsd_scpt.c | 7 +--- js/jsd/jsd_xpc.cpp | 55 +++++++++++++++++++++------ js/jsd/jsd_xpc.h | 2 +- js/src/jsapi.cpp | 3 ++ js/src/jscntxt.h | 5 +++ js/src/jscompartment.cpp | 2 +- js/src/jsdbgapi.cpp | 6 +++ js/src/jsdbgapi.h | 7 ++++ js/src/xpconnect/idl/nsIXPConnect.idl | 11 +++++- js/src/xpconnect/src/nsXPConnect.cpp | 44 +++++++++++++++++++++ js/src/xpconnect/src/xpcprivate.h | 3 ++ 13 files changed, 157 insertions(+), 33 deletions(-) diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 84ccd6220961..96c689ec9940 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -993,10 +993,6 @@ nsJSContext::DOMOperationCallback(JSContext *cx) 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 diff --git a/js/jsd/idl/jsdIDebuggerService.idl b/js/jsd/idl/jsdIDebuggerService.idl index ea250cf525f2..c9c2c3b03f9e 100644 --- a/js/jsd/idl/jsdIDebuggerService.idl +++ b/js/jsd/idl/jsdIDebuggerService.idl @@ -72,12 +72,13 @@ 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. */ @@ -216,19 +217,34 @@ interface jsdIDebuggerService : nsISupports */ 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. - */ - void on (); - /** - * 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. + * 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. */ - [noscript] void onForRuntime (in JSRuntime rt); + void asyncOn (in jsdIActivationCallback callback); + + /** + * Called by nsIXPConnect after it's had a chance to recompile for + * debug mode. + */ + [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 @@ -457,6 +473,15 @@ interface jsdIFilter : nsISupports 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. */ diff --git a/js/jsd/jsd_scpt.c b/js/jsd/jsd_scpt.c index 01e96c0668fb..37e08b0aa2df 100644 --- a/js/jsd/jsd_scpt.c +++ b/js/jsd/jsd_scpt.c @@ -585,11 +585,6 @@ jsd_NewScriptHookProc( 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); @@ -611,7 +606,7 @@ jsd_NewScriptHookProc( if( hook ) hook(jsdc, jsdscript, JS_TRUE, hookData); -} +} void jsd_DestroyScriptHookProc( diff --git a/js/jsd/jsd_xpc.cpp b/js/jsd/jsd_xpc.cpp index d044f1eafde0..dfba4dbf5574 100644 --- a/js/jsd/jsd_xpc.cpp +++ b/js/jsd/jsd_xpc.cpp @@ -2396,6 +2396,12 @@ jsdService::GetIsOn (PRBool *_rval) NS_IMETHODIMP jsdService::On (void) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +jsdService::AsyncOn (jsdIActivationCallback *activationCallback) { nsresult rv; @@ -2410,18 +2416,32 @@ jsdService::On (void) 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 */ @@ -2471,6 +2491,10 @@ jsdService::OnForRuntime (JSRuntime *rt) #ifdef DEBUG printf ("+++ JavaScript debugging hooks installed.\n"); #endif + + if (mActivationCallback) + return mActivationCallback->OnDebuggerActivated(); + return NS_OK; } @@ -2521,6 +2545,13 @@ jsdService::Off (void) printf ("+++ JavaScript debugging hooks removed.\n"); #endif + nsresult rv; + nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); + if (NS_FAILED(rv)) + return rv; + + xpc->SetDebugModeWhenPossible(PR_FALSE); + return NS_OK; } @@ -2969,7 +3000,7 @@ 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; @@ -3013,7 +3044,7 @@ 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; @@ -3041,7 +3072,7 @@ 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; @@ -3069,7 +3100,7 @@ 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; @@ -3097,7 +3128,7 @@ 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; @@ -3125,7 +3156,7 @@ 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; @@ -3153,7 +3184,7 @@ 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; @@ -3181,7 +3212,7 @@ 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; @@ -3274,7 +3305,7 @@ jsdASObserver::Observe (nsISupports *aSubject, const char *aTopic, if (NS_FAILED(rv)) return rv; - rv = jsds->OnForRuntime(rt); + rv = jsds->ActivateDebugger(rt); if (NS_FAILED(rv)) return rv; diff --git a/js/jsd/jsd_xpc.h b/js/jsd/jsd_xpc.h index 92f08799242c..06e5c2aeab83 100644 --- a/js/jsd/jsd_xpc.h +++ b/js/jsd/jsd_xpc.h @@ -305,7 +305,7 @@ class jsdService : public jsdIDebuggerService nsCOMPtr mThrowHook; nsCOMPtr mTopLevelHook; nsCOMPtr mFunctionHook; - + nsCOMPtr mActivationCallback; }; #endif /* JSDSERVICE_H___ */ diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 3d4807861e0a..07fa8728ee0c 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -653,6 +653,9 @@ JSRuntime::init(uint32 maxbytes) if (!debuggerLock) return false; #endif + + debugMode = JS_FALSE; + return propertyTree.init() && js_InitThreads(this); } diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 8a90708ea7c6..f598696023c9 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -1416,6 +1416,11 @@ struct JSRuntime { /* 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 { diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index d523ec261e4f..705933870891 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -53,7 +53,7 @@ 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); diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index 21e3bf5212ab..05d03e5eab49 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -108,6 +108,12 @@ IsScriptLive(JSContext *cx, JSScript *script) } #endif +JS_PUBLIC_API(void) +JS_SetRuntimeDebugMode(JSRuntime *rt, JSBool debug) +{ + rt->debugMode = debug; +} + JS_FRIEND_API(JSBool) js_SetDebugMode(JSContext *cx, JSBool debug) { diff --git a/js/src/jsdbgapi.h b/js/src/jsdbgapi.h index 7bc7a4aaa0ae..7b16c5e79947 100644 --- a/js/src/jsdbgapi.h +++ b/js/src/jsdbgapi.h @@ -49,6 +49,13 @@ 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 diff --git a/js/src/xpconnect/idl/nsIXPConnect.idl b/js/src/xpconnect/idl/nsIXPConnect.idl index eb44d356091d..5a483ea2e7c9 100644 --- a/js/src/xpconnect/idl/nsIXPConnect.idl +++ b/js/src/xpconnect/idl/nsIXPConnect.idl @@ -399,7 +399,7 @@ interface nsIXPCFunctionThisTranslator : nsISupports { 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } } %} -[uuid(fb780ace-dced-432b-bb82-8df7d4f919c8)] +[uuid(c825b64b-d537-4e53-822e-547049aae9c9)] interface nsIXPConnect : nsISupports { %{ C++ @@ -827,4 +827,13 @@ interface nsIXPConnect : nsISupports */ [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); }; diff --git a/js/src/xpconnect/src/nsXPConnect.cpp b/js/src/xpconnect/src/nsXPConnect.cpp index 1711f54c8d05..71ec35efbfa0 100644 --- a/js/src/xpconnect/src/nsXPConnect.cpp +++ b/js/src/xpconnect/src/nsXPConnect.cpp @@ -61,6 +61,8 @@ #include "WrapperFactory.h" #include "AccessCheck.h" +#include "jsdIDebuggerService.h" + NS_IMPL_THREADSAFE_ISUPPORTS6(nsXPConnect, nsIXPConnect, nsISupportsWeakReference, @@ -72,6 +74,8 @@ NS_IMPL_THREADSAFE_ISUPPORTS6(nsXPConnect, 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) @@ -2407,6 +2411,30 @@ nsXPConnect::Peek(JSContext * *_retval) 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 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) @@ -2432,6 +2460,15 @@ nsXPConnect::Push(JSContext * 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); } @@ -2539,6 +2576,13 @@ nsXPConnect::GetCaller(JSContext **aJSContext, JSObject **aObj) *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() diff --git a/js/src/xpconnect/src/xpcprivate.h b/js/src/xpconnect/src/xpcprivate.h index e749c7de2273..aaed5d80712b 100644 --- a/js/src/xpconnect/src/xpcprivate.h +++ b/js/src/xpconnect/src/xpcprivate.h @@ -626,6 +626,9 @@ private: nsCOMPtr mBackstagePass; static PRUint32 gReportAllJSExceptions; + static JSBool gDebugMode; + static JSBool gDesiredDebugMode; + static inline void CheckForDebugMode(JSRuntime *rt); public: static nsIScriptSecurityManager *gScriptSecurityManager;