/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * 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.org code * * The Initial Developer of the Original Code is * Netscape Communications Corporation * Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. * * Alternatively, the contents of this file may be used under the * terms of the GNU Public License (the "GPL"), in which case the * provisions of the GPL are applicable instead of those above. * If you wish to allow use of your version of this file only * under the terms of the GPL and not to allow others to use your * version of this file under the MPL, indicate your decision by * deleting the provisions above and replace them with the notice * and other provisions required by the GPL. If you do not delete * the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * Contributor(s): * Robert Ginda, * */ #include "jsd_xpc.h" #include "nsIXPConnect.h" #include "nsIGenericFactory.h" #include "nsIServiceManager.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIPref.h" #include "nsICategoryManager.h" #include "nsIJSRuntimeService.h" #include "nsMemory.h" #include "jsdebug.h" /* XXX this stuff is used by NestEventLoop, a temporary hack to be refactored * later */ #include "nsWidgetsCID.h" #include "nsIAppShell.h" #include "nsIJSContextStack.h" #ifdef DEBUG_verbose # define DEBUG_COUNT(name, count) \ { if ((count % 10) == 0) printf (name ": %i\n", count); } # define DEBUG_CREATE(name, count) {count++; DEBUG_COUNT ("+++++ "name,count)} # define DEBUG_DESTROY(name, count) {count--; DEBUG_COUNT ("----- "name,count)} #else # define DEBUG_CREATE (name, count) # define DEBUG_DESTROY (name, count) #endif #define ASSERT_VALID_CONTEXT { if (!mCx) return NS_ERROR_NOT_AVAILABLE; } #define ASSERT_VALID_FRAME { if (!mValid) return NS_ERROR_NOT_AVAILABLE; } #define ASSERT_VALID_PROPERTY { if (!mValid) return NS_ERROR_NOT_AVAILABLE; } #define ASSERT_VALID_SCRIPT { if (!mValid) return NS_ERROR_NOT_AVAILABLE; } #define ASSERT_VALID_VALUE { if (!mValid) return NS_ERROR_NOT_AVAILABLE; } #define JSDSERVICE_CID \ { /* f1299dc2-1dd1-11b2-a347-ee6b7660e048 */ \ 0xf1299dc2, \ 0x1dd1, \ 0x11b2, \ {0xa3, 0x47, 0xee, 0x6b, 0x76, 0x60, 0xe0, 0x48} \ } #define JSDASO_CID \ { /* f2723a7e-1dd1-11b2-9f9e-ff701f717575 */ \ 0xf2723a7e, \ 0x1dd1, \ 0x11b2, \ {0x9f, 0x9e, 0xff, 0x70, 0x1f, 0x71, 0x75, 0x75} \ } #define NS_CATMAN_CTRID "@mozilla.org/categorymanager;1" #define NS_PREF_CTRID "@mozilla.org/preferences;1" #define NS_JSRT_CTRID "@mozilla.org/js/xpc/RuntimeService;1" #define APPSTART_CATEGORY "app-startup" #define PROFILE_CHANGE_EVENT "profile-after-change" static JSBool jsds_GCCallbackProc (JSContext *cx, JSGCStatus status); /******************************************************************************* * global vars *******************************************************************************/ static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); const char jsdServiceContractID[] = "@mozilla.org/js/jsd/debugger-service;1"; const char jsdASObserverContractID[] = "@mozilla.org/js/jsd/app-start-observer;1"; #ifdef DEBUG_verbose PRUint32 gScriptCount = 0; PRUint32 gValueCount = 0; PRUint32 gPropertyCount = 0; #endif static jsdService *gJsds = 0; static JSGCCallback gLastGCProc = jsds_GCCallbackProc; static JSGCStatus gGCStatus = JSGC_END; static struct DeadScript { PRCList links; JSDContext *jsdc; jsdIScript *script; } *gDeadScripts = nsnull; static struct LiveEphemeral *gLiveValues = nsnull, *gLiveProperties = nsnull; /******************************************************************************* * utility functions for ephemeral lists *******************************************************************************/ void jsds_InvalidateAllEphemerals (LiveEphemeral **listHead) { LiveEphemeral *lv_record = NS_REINTERPRET_CAST (LiveEphemeral *, PR_NEXT_LINK(&(*listHead)->links)); while (*listHead) { LiveEphemeral *next = NS_REINTERPRET_CAST (LiveEphemeral *, PR_NEXT_LINK(&lv_record->links)); lv_record->value->Invalidate(); lv_record = next; } } void jsds_InsertEphemeral (LiveEphemeral **listHead, LiveEphemeral *item) { if (*listHead) { /* if the list exists, add to it */ PR_APPEND_LINK(&item->links, &(*listHead)->links); } else { /* otherwise create the list */ PR_INIT_CLIST(&item->links); *listHead = item; } } void jsds_RemoveEphemeral (LiveEphemeral **listHead, LiveEphemeral *item) { LiveEphemeral *next = NS_REINTERPRET_CAST (LiveEphemeral *, PR_NEXT_LINK(&item->links)); if (next == item) { /* if the current item is also the next item, we're the only element, * null out the list head */ NS_ASSERTION (*listHead == item, "How could we not be the head of a one item list?"); *listHead = nsnull; } else if (item == *listHead) { /* otherwise, if we're currently the list head, change it */ *listHead = next; } PR_REMOVE_AND_INIT_LINK(&item->links); } /******************************************************************************* * c callbacks *******************************************************************************/ static void jsds_NotifyPendingDeadScripts () { nsCOMPtr hook = 0; gJsds->GetScriptHook (getter_AddRefs(hook)); if (hook) { DeadScript *ds; do { ds = gDeadScripts; /* tell the user this script has been destroyed */ hook->OnScriptDestroyed (ds->script); /* get next deleted script */ gDeadScripts = NS_REINTERPRET_CAST(DeadScript *, PR_NEXT_LINK(&ds->links)); /* take ourselves out of the circular list */ PR_REMOVE_LINK(&ds->links); /* addref came from the FromPtr call in jsds_ScriptHookProc */ NS_RELEASE(ds->script); /* free the struct! */ PR_Free(ds); } while (&gDeadScripts->links != &ds->links); /* keep going until we catch up with our tail */ } gDeadScripts = 0; } static JSBool jsds_GCCallbackProc (JSContext *cx, JSGCStatus status) { gGCStatus = status; #ifdef DEBUG printf ("new gc status is %i\n", status); #endif if (status == JSGC_END && gDeadScripts) jsds_NotifyPendingDeadScripts (); if (gLastGCProc) return gLastGCProc (cx, status); return JS_TRUE; } static PRUint32 jsds_ExecutionHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate, uintN type, void* callerdata, jsval* rval) { nsCOMPtr hook(0); PRUint32 hook_rv = JSD_HOOK_RETURN_CONTINUE; jsdIValue *js_rv = 0; switch (type) { case JSD_HOOK_INTERRUPTED: gJsds->GetInterruptHook(getter_AddRefs(hook)); break; case JSD_HOOK_DEBUG_REQUESTED: gJsds->GetErrorHook(getter_AddRefs(hook)); break; case JSD_HOOK_DEBUGGER_KEYWORD: gJsds->GetDebuggerHook(getter_AddRefs(hook)); break; case JSD_HOOK_BREAKPOINT: gJsds->GetBreakpointHook(getter_AddRefs(hook)); break; case JSD_HOOK_THROW: { gJsds->GetThrowHook(getter_AddRefs(hook)); if (hook) { JSDValue *jsdv = JSD_GetException (jsdc, jsdthreadstate); js_rv = jsdValue::FromPtr (jsdc, jsdv); } break; } default: NS_ASSERTION (0, "Unknown hook type."); } if (!hook) return NS_OK; JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate); nsCOMPtr frame = getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, native_frame)); hook->OnExecute (frame, type, &js_rv, &hook_rv); frame->Invalidate(); if (hook_rv == JSD_HOOK_RETURN_RET_WITH_VAL || hook_rv == JSD_HOOK_RETURN_THROW_WITH_VAL) { JSDValue *jsdv; js_rv->GetJSDValue (&jsdv); *rval = JSD_GetValueWrappedJSVal(jsdc, jsdv); } NS_IF_RELEASE(js_rv); return hook_rv; } static void jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, void* callerdata) { if (creating) { jsdIScriptHook *hook = 0; gJsds->GetScriptHook (&hook); if (!hook) return; nsCOMPtr script = getter_AddRefs(jsdScript::FromPtr(jsdc, jsdscript)); hook->OnScriptCreated (script); } else { jsdIScript *jsdis = jsdScript::FromPtr(jsdc, jsdscript); /* the initial addref is owned by the DeadScript record */ jsdis->Invalidate(); if (gGCStatus == JSGC_END) { /* if GC *isn't* running, we can tell the user about the script * delete now. */ nsCOMPtr hook = 0; gJsds->GetScriptHook (getter_AddRefs(hook)); if (hook) { hook->OnScriptDestroyed (jsdis); } } 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); if (!ds) { NS_RELEASE(jsdis); return; /* NS_ERROR_OUT_OF_MEMORY */ } ds->jsdc = jsdc; ds->script = jsdis; if (gDeadScripts) /* if the queue exists, add to it */ PR_APPEND_LINK(&ds->links, &gDeadScripts->links); else { /* otherwise create the queue */ PR_INIT_CLIST(&ds->links); gDeadScripts = ds; } } } } /******************************************************************************* * reflected jsd data structures *******************************************************************************/ /* Contexts */ /* NS_IMPL_THREADSAFE_ISUPPORTS1(jsdContext, jsdIContext); NS_IMETHODIMP jsdContext::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } */ /* Objects */ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdObject, jsdIObject); NS_IMETHODIMP jsdObject::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdObject::GetJSDObject(JSDObject **_rval) { *_rval = mObject; return NS_OK; } NS_IMETHODIMP jsdObject::GetCreatorURL(char **_rval) { *_rval = nsCString(JSD_GetObjectNewURL(mCx, mObject)).ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdObject::GetCreatorLine(PRUint32 *_rval) { *_rval = JSD_GetObjectNewLineNumber(mCx, mObject); return NS_OK; } NS_IMETHODIMP jsdObject::GetConstructorURL(char **_rval) { *_rval = nsCString(JSD_GetObjectConstructorURL(mCx, mObject)).ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdObject::GetConstructorLine(PRUint32 *_rval) { *_rval = JSD_GetObjectConstructorLineNumber(mCx, mObject); return NS_OK; } NS_IMETHODIMP jsdObject::GetValue(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetValueForObject (mCx, mObject); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } /* PC */ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdPC, jsdIPC); NS_IMETHODIMP jsdPC::GetPc(jsuword *_rval) { *_rval = mPC; return NS_OK; } /* Properties */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdProperty, jsdIProperty, jsdIEphemeral); jsdProperty::jsdProperty (JSDContext *aCx, JSDProperty *aProperty) : mCx(aCx), mProperty(aProperty) { DEBUG_CREATE ("jsdProperty", gPropertyCount); mValid = (aCx && aProperty); NS_INIT_ISUPPORTS(); mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveProperties, &mLiveListEntry); } jsdProperty::~jsdProperty () { DEBUG_DESTROY ("jsdProperty", gPropertyCount); if (mValid) Invalidate(); } NS_IMETHODIMP jsdProperty::Invalidate() { ASSERT_VALID_VALUE; mValid = PR_FALSE; jsds_RemoveEphemeral (&gLiveProperties, &mLiveListEntry); JSD_DropProperty (mCx, mProperty); return NS_OK; } void jsdProperty::InvalidateAll() { if (gLiveProperties) jsds_InvalidateAllEphemerals (&gLiveProperties); } NS_IMETHODIMP jsdProperty::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdProperty::GetJSDProperty(JSDProperty **_rval) { *_rval = mProperty; return NS_OK; } NS_IMETHODIMP jsdProperty::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdProperty::GetAlias(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetPropertyValue (mCx, mProperty); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdProperty::GetFlags(PRUint32 *_rval) { *_rval = JSD_GetPropertyFlags (mCx, mProperty); return NS_OK; } NS_IMETHODIMP jsdProperty::GetName(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetPropertyName (mCx, mProperty); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdProperty::GetValue(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetPropertyValue (mCx, mProperty); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdProperty::GetVarArgSlot(PRUint32 *_rval) { *_rval = JSD_GetPropertyVarArgSlot (mCx, mProperty); return NS_OK; } /* Scripts */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdScript, jsdIScript, jsdIEphemeral); jsdScript::jsdScript (JSDContext *aCx, JSDScript *aScript) : mValid(PR_FALSE), mCx(aCx), mScript(aScript), mFileName(0), mFunctionName(0), mBaseLineNumber(0), mLineExtent(0) { DEBUG_CREATE ("jsdScript", gScriptCount); NS_INIT_ISUPPORTS(); if (mScript) { /* copy the script's information now, so we have it later, when it * gets destroyed. */ JSD_LockScriptSubsystem(mCx); mFileName = new nsCString(JSD_GetScriptFilename(mCx, mScript)); mFunctionName = new nsCString(JSD_GetScriptFunctionName(mCx, mScript)); mBaseLineNumber = JSD_GetScriptBaseLineNumber(mCx, mScript); mLineExtent = JSD_GetScriptLineExtent(mCx, mScript); JSD_UnlockScriptSubsystem(mCx); mValid = true; } } jsdScript::~jsdScript () { DEBUG_DESTROY ("jsdScript", gScriptCount); if (mFileName) delete mFileName; if (mFunctionName) delete mFunctionName; /* Invalidate() needs to be called to release an owning reference to * ourselves, so if we got here without being invalidated, something * has gone wrong with our ref count. */ NS_ASSERTION (!mValid, "Script destroyed without being invalidated."); } NS_IMETHODIMP jsdScript::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_SCRIPT; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdScript::GetJSDScript(JSDScript **_rval) { ASSERT_VALID_SCRIPT; *_rval = mScript; return NS_OK; } NS_IMETHODIMP jsdScript::Invalidate() { ASSERT_VALID_SCRIPT; mValid = PR_FALSE; /* release the addref we do in FromPtr */ jsdIScript *script = NS_STATIC_CAST(jsdIScript *, JSD_GetScriptPrivate(mScript)); NS_ASSERTION (script == this, "That's not my script!"); NS_RELEASE(script); return NS_OK; } NS_IMETHODIMP jsdScript::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdScript::GetIsActive(PRBool *_rval) { if (!mValid) { *_rval = PR_FALSE; return NS_OK; } JSD_LockScriptSubsystem(mCx); *_rval = JSD_IsActiveScript(mCx, mScript); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } NS_IMETHODIMP jsdScript::GetFileName(char **_rval) { *_rval = mFileName->ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionName(char **_rval) { *_rval = mFunctionName->ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdScript::GetBaseLineNumber(PRUint32 *_rval) { *_rval = mBaseLineNumber; return NS_OK; } NS_IMETHODIMP jsdScript::GetLineExtent(PRUint32 *_rval) { *_rval = mLineExtent; return NS_OK; } NS_IMETHODIMP jsdScript::PcToLine(jsdIPC *aPC, PRUint32 *_rval) { ASSERT_VALID_SCRIPT; jsuword pc; aPC->GetPc(&pc); *_rval = JSD_GetClosestLine (mCx, mScript, pc); return NS_OK; } NS_IMETHODIMP jsdScript::LineToPc(PRUint32 aLine, jsdIPC **_rval) { ASSERT_VALID_SCRIPT; jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); *_rval = jsdPC::FromPtr (pc); return NS_OK; } NS_IMETHODIMP jsdScript::SetBreakpoint(jsdIPC *aPC) { ASSERT_VALID_SCRIPT; jsuword pc; aPC->GetPc (&pc); JSD_SetExecutionHook (mCx, mScript, pc, jsds_ExecutionHookProc, NS_REINTERPRET_CAST(void *, PRIVATE_TO_JSVAL(NULL))); return NS_OK; } NS_IMETHODIMP jsdScript::ClearBreakpoint(jsdIPC *aPC) { ASSERT_VALID_SCRIPT; if (!aPC) return NS_ERROR_INVALID_ARG; jsuword pc; aPC->GetPc (&pc); JSD_ClearExecutionHook (mCx, mScript, pc); return NS_OK; } NS_IMETHODIMP jsdScript::ClearAllBreakpoints() { ASSERT_VALID_SCRIPT; JSD_LockScriptSubsystem(mCx); JSD_ClearAllExecutionHooksForScript (mCx, mScript); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } /* Stack Frames */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdStackFrame, jsdIStackFrame, jsdIEphemeral); NS_IMETHODIMP jsdStackFrame::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_FRAME; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetJSDThreadState(JSDThreadState **_rval) { ASSERT_VALID_FRAME; *_rval = mThreadState; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetJSDStackFrameInfo(JSDStackFrameInfo **_rval) { ASSERT_VALID_FRAME; *_rval = mStackFrameInfo; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdStackFrame::Invalidate() { ASSERT_VALID_FRAME; mValid = PR_FALSE; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetCallingFrame(jsdIStackFrame **_rval) { ASSERT_VALID_FRAME; JSDStackFrameInfo *sfi = JSD_GetCallingStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdStackFrame::FromPtr (mCx, mThreadState, sfi); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetScript(jsdIScript **_rval) { ASSERT_VALID_FRAME; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdScript::FromPtr (mCx, script); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetPc(jsdIPC **_rval) { ASSERT_VALID_FRAME; jsuword pc; pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdPC::FromPtr (pc); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetLine(PRUint32 *_rval) { ASSERT_VALID_FRAME; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); jsuword pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = JSD_GetClosestLine (mCx, script, pc); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetCallee(jsdIValue **_rval) { ASSERT_VALID_FRAME; JSDValue *jsdv = JSD_GetCallObjectForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetScope(jsdIValue **_rval) { ASSERT_VALID_FRAME; JSDValue *jsdv = JSD_GetScopeChainForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetThisValue(jsdIValue **_rval) { ASSERT_VALID_FRAME; JSDValue *jsdv = JSD_GetThisForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::Eval (const nsAReadableString &bytes, const char *fileName, PRUint32 line, jsdIValue **_rval) { ASSERT_VALID_FRAME; jsval jv; const nsSharedBufferHandle *h = bytes.GetSharedBufferHandle(); const jschar *char_bytes = NS_REINTERPRET_CAST(const jschar *, h->DataStart()); if (!JSD_EvaluateUCScriptInStackFrame (mCx, mThreadState, mStackFrameInfo, char_bytes, bytes.Length(), fileName, line, &jv)) return NS_ERROR_FAILURE; JSDValue *jsdv = JSD_NewValue (mCx, jv); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } /* Values */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdValue, jsdIValue, jsdIEphemeral); jsdValue::jsdValue (JSDContext *aCx, JSDValue *aValue) : mValid(PR_TRUE), mCx(aCx), mValue(aValue) { DEBUG_CREATE ("jsdValue", gValueCount); NS_INIT_ISUPPORTS(); mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveValues, &mLiveListEntry); } jsdValue::~jsdValue() { DEBUG_DESTROY ("jsdValue", gValueCount); if (mValid) /* call Invalidate() to take ourselves out of the live list */ Invalidate(); } NS_IMETHODIMP jsdValue::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdValue::Invalidate() { ASSERT_VALID_VALUE; mValid = PR_FALSE; jsds_RemoveEphemeral (&gLiveValues, &mLiveListEntry); JSD_DropValue (mCx, mValue); return NS_OK; } void jsdValue::InvalidateAll() { if (gLiveValues) jsds_InvalidateAllEphemerals (&gLiveValues); } NS_IMETHODIMP jsdValue::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_VALUE; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdValue::GetJSDValue (JSDValue **_rval) { ASSERT_VALID_VALUE; *_rval = mValue; return NS_OK; } NS_IMETHODIMP jsdValue::GetIsNative (PRBool *_rval) { ASSERT_VALID_VALUE; *_rval = JSD_IsValueNative (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIsNumber (PRBool *_rval) { ASSERT_VALID_VALUE; *_rval = JSD_IsValueNumber (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIsPrimitive (PRBool *_rval) { ASSERT_VALID_VALUE; *_rval = JSD_IsValuePrimitive (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsType (PRUint32 *_rval) { ASSERT_VALID_VALUE; /* XXX surely this can be done better. */ if (JSD_IsValueBoolean(mCx, mValue)) *_rval = TYPE_BOOLEAN; else if (JSD_IsValueDouble(mCx, mValue)) *_rval = TYPE_DOUBLE; else if (JSD_IsValueInt(mCx, mValue)) *_rval = TYPE_INT; else if (JSD_IsValueFunction(mCx, mValue)) *_rval = TYPE_FUNCTION; else if (JSD_IsValueNull(mCx, mValue)) *_rval = TYPE_NULL; else if (JSD_IsValueObject(mCx, mValue)) *_rval = TYPE_OBJECT; else if (JSD_IsValueString(mCx, mValue)) *_rval = TYPE_STRING; else if (JSD_IsValueVoid(mCx, mValue)) *_rval = TYPE_VOID; else *_rval = TYPE_UNKNOWN; return NS_OK; } NS_IMETHODIMP jsdValue::GetJsPrototype (jsdIValue **_rval) { ASSERT_VALID_VALUE; JSDValue *jsdv = JSD_GetValuePrototype (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsParent (jsdIValue **_rval) { ASSERT_VALID_VALUE; JSDValue *jsdv = JSD_GetValueParent (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsClassName(char **_rval) { ASSERT_VALID_VALUE; *_rval = nsCString(JSD_GetValueClassName(mCx, mValue)).ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsConstructor (jsdIValue **_rval) { ASSERT_VALID_VALUE; JSDValue *jsdv = JSD_GetValueConstructor (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsFunctionName(char **_rval) { ASSERT_VALID_VALUE; *_rval = nsCString(JSD_GetValueFunctionName(mCx, mValue)).ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdValue::GetBooleanValue(PRBool *_rval) { ASSERT_VALID_VALUE; *_rval = JSD_GetValueBoolean (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetDoubleValue(double *_rval) { ASSERT_VALID_VALUE; *_rval = *JSD_GetValueDouble (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIntValue(PRInt32 *_rval) { ASSERT_VALID_VALUE; *_rval = JSD_GetValueInt (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetObjectValue(jsdIObject **_rval) { ASSERT_VALID_VALUE; JSDObject *obj; obj = JSD_GetObjectForValue (mCx, mValue); *_rval = jsdObject::FromPtr (mCx, obj); return NS_OK; } NS_IMETHODIMP jsdValue::GetStringValue(char **_rval) { ASSERT_VALID_VALUE; JSString *jstr_val = JSD_GetValueString(mCx, mValue); *_rval = nsCString(JS_GetStringBytes(jstr_val)).ToNewCString(); return NS_OK; } NS_IMETHODIMP jsdValue::GetPropertyCount (PRInt32 *_rval) { ASSERT_VALID_VALUE; if (JSD_IsValueObject(mCx, mValue)) *_rval = JSD_GetCountOfProperties (mCx, mValue); else *_rval = -1; return NS_OK; } NS_IMETHODIMP jsdValue::GetProperties (jsdIProperty ***propArray, PRUint32 *length) { ASSERT_VALID_VALUE; if (!JSD_IsValueObject(mCx, mValue)) { *length = 0; *propArray = 0; return NS_OK; } jsdIProperty **pa_temp; PRUint32 prop_count = JSD_GetCountOfProperties (mCx, mValue); pa_temp = NS_STATIC_CAST(jsdIProperty **, nsMemory::Alloc(sizeof (jsdIProperty *) * prop_count)); PRUint32 i = 0; JSDProperty *iter = NULL; JSDProperty *prop; while ((prop = JSD_IterateProperties (mCx, mValue, &iter))) { pa_temp[i] = jsdProperty::FromPtr (mCx, prop); ++i; } NS_ASSERTION (prop_count == i, "property count mismatch"); /* if caller doesn't care about length, don't bother telling them */ *propArray = pa_temp; if (length) *length = prop_count; return NS_OK; } NS_IMETHODIMP jsdValue::GetProperty (const char *name, jsdIProperty **_rval) { ASSERT_VALID_VALUE; JSContext *cx = JSD_GetDefaultJSContext (mCx); /* not rooting this */ JSString *jstr_name = JS_NewStringCopyZ (cx, name); JSDProperty *prop = JSD_GetValueProperty (mCx, mValue, jstr_name); *_rval = jsdProperty::FromPtr (mCx, prop); return NS_OK; } NS_IMETHODIMP jsdValue::Refresh() { ASSERT_VALID_VALUE; JSD_RefreshValue (mCx, mValue); return NS_OK; } /****************************************************************************** * debugger service implementation ******************************************************************************/ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdService, jsdIDebuggerService); NS_IMETHODIMP jsdService::GetIsOn (PRBool *_rval) { *_rval = mOn; return NS_OK; } NS_IMETHODIMP jsdService::On (void) { nsresult rv; /* get JS things from the CallContext */ nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); if (!xpc) return NS_ERROR_FAILURE; nsCOMPtr cc; rv = xpc->GetCurrentNativeCallContext(getter_AddRefs(cc)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; JSContext *cx; rv = cc->GetJSContext (&cx); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; return OnForRuntime(JS_GetRuntime (cx)); } NS_IMETHODIMP jsdService::OnForRuntime (JSRuntime *rt) { if (mOn) return (rt == mRuntime) ? NS_OK : NS_ERROR_ALREADY_INITIALIZED; mRuntime = rt; 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; /* If any of these mFooHook objects are installed, do the required JSD * hookup now. See also, jsdService::SetFooHook(). */ if (mThrowHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); if (mScriptHook) JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); if (mInterruptHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebuggerHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); if (mErrorHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); mOn = PR_TRUE; return NS_OK; } NS_IMETHODIMP jsdService::Off (void) { if (!mCx || !mRuntime) return NS_ERROR_NOT_INITIALIZED; if (gDeadScripts) if (gGCStatus == JSGC_END) jsds_NotifyPendingDeadScripts(); else return NS_ERROR_NOT_AVAILABLE; /* if (gLastGCProc != jsds_GCCallbackProc) JS_SetGCCallbackRT (mRuntime, gLastGCProc); */ jsdValue::InvalidateAll(); jsdProperty::InvalidateAll(); ClearAllBreakpoints(); JSD_SetThrowHook (mCx, NULL, NULL); JSD_SetScriptHook (mCx, NULL, NULL); JSD_SetInterruptHook (mCx, NULL, NULL); JSD_SetDebuggerHook (mCx, NULL, NULL); JSD_SetDebugBreakHook (mCx, NULL, NULL); JSD_DebuggerOff (mCx); mCx = nsnull; mRuntime = nsnull; mOn = PR_FALSE; return NS_OK; } NS_IMETHODIMP jsdService::EnumerateScripts (jsdIScriptEnumerator *enumerator) { ASSERT_VALID_CONTEXT; JSDScript *script; JSDScript *iter = NULL; PRBool cont_flag; nsresult rv = NS_OK; JSD_LockScriptSubsystem(mCx); while((script = JSD_IterateScripts(mCx, &iter)) != NULL) { rv = enumerator->EnumerateScript (jsdScript::FromPtr(mCx, script), &cont_flag); if (NS_FAILED(rv) || !cont_flag) break; } JSD_UnlockScriptSubsystem(mCx); return rv; } NS_IMETHODIMP jsdService::ClearAllBreakpoints (void) { ASSERT_VALID_CONTEXT; JSD_LockScriptSubsystem(mCx); JSD_ClearAllExecutionHooks (mCx); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } NS_IMETHODIMP jsdService::EnterNestedEventLoop (PRUint32 *_rval) { nsCOMPtr appShell(do_CreateInstance(kAppShellCID)); NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); appShell->Create(0, nsnull); appShell->Spinup(); // Store locally so it doesn't die on us nsCOMPtr stack(do_GetService("@mozilla.org/js/xpc/ContextStack;1")); nsresult rv = NS_OK; PRUint32 nestLevel = ++mNestedLoopLevel; if(stack && NS_SUCCEEDED(stack->Push(nsnull))) { while(NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) { void* data; PRBool isRealEvent; //PRBool processEvent; rv = appShell->GetNativeEvent(isRealEvent, data); if(NS_SUCCEEDED(rv)) { appShell->DispatchNativeEvent(isRealEvent, data); } } JSContext* cx; stack->Pop(&cx); NS_ASSERTION(cx == nsnull, "JSContextStack mismatch"); } else rv = NS_ERROR_FAILURE; appShell->Spindown(); NS_ASSERTION (mNestedLoopLevel <= nestLevel, "nested event didn't unwind properly"); if (mNestedLoopLevel == nestLevel) --mNestedLoopLevel; *_rval = mNestedLoopLevel; return rv; } NS_IMETHODIMP jsdService::ExitNestedEventLoop (PRUint32 *_rval) { if (mNestedLoopLevel > 0) --mNestedLoopLevel; else return NS_ERROR_FAILURE; *_rval = mNestedLoopLevel; return NS_OK; } /* hook attribute get/set functions */ NS_IMETHODIMP jsdService::SetBreakpointHook (jsdIExecutionHook *aHook) { mBreakpointHook = aHook; return NS_OK; } NS_IMETHODIMP jsdService::GetBreakpointHook (jsdIExecutionHook **aHook) { *aHook = mBreakpointHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetErrorHook (jsdIExecutionHook *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. */ if (!mCx) return NS_OK; if (aHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearDebugBreakHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetErrorHook (jsdIExecutionHook **aHook) { *aHook = mErrorHook; NS_IF_ADDREF(*aHook); return NS_OK; } 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. */ if (!mCx) return NS_OK; if (aHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearDebuggerHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetDebuggerHook (jsdIExecutionHook **aHook) { *aHook = mDebuggerHook; NS_IF_ADDREF(*aHook); return NS_OK; } 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. */ if (!mCx) return NS_OK; if (aHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearInterruptHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetInterruptHook (jsdIExecutionHook **aHook) { *aHook = mInterruptHook; NS_IF_ADDREF(*aHook); return NS_OK; } 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. */ if (!mCx) return NS_OK; if (aHook) JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); else JSD_SetScriptHook (mCx, NULL, NULL); return NS_OK; } NS_IMETHODIMP jsdService::GetScriptHook (jsdIScriptHook **aHook) { *aHook = mScriptHook; NS_IF_ADDREF(*aHook); return NS_OK; } 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. */ if (!mCx) return NS_OK; if (aHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); else JSD_ClearThrowHook (mCx); return NS_OK; } NS_IMETHODIMP jsdService::GetThrowHook (jsdIExecutionHook **aHook) { *aHook = mThrowHook; NS_IF_ADDREF(*aHook); return NS_OK; } jsdService * jsdService::GetService () { if (!gJsds) gJsds = new jsdService(); NS_IF_ADDREF(gJsds); return gJsds; } NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(jsdService, jsdService::GetService); /* app-start observer. turns on the debugger at app-start if * js.debugger.autostart pref is true */ class jsdASObserver : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER jsdASObserver () { NS_INIT_ISUPPORTS(); } }; NS_IMPL_THREADSAFE_ISUPPORTS1(jsdASObserver, nsIObserver); NS_IMETHODIMP jsdASObserver::Observe (nsISupports *aSubject, const PRUnichar *aTopic, const PRUnichar *aData) { nsresult rv; if (!nsCRT::strcmp(aTopic, NS_LITERAL_STRING(PROFILE_CHANGE_EVENT).get())) { /* profile change means that the prefs file is loaded, so we can finally * check to see if we're supposed to start the debugger. */ nsCOMPtr pref = do_GetService (NS_PREF_CTRID); if (!pref) return NS_ERROR_FAILURE; PRBool f = PR_FALSE; rv = pref->GetBoolPref("js.debugger.autostart", &f); if (NS_SUCCEEDED(rv) && f) { jsdService *jsds = jsdService::GetService(); nsCOMPtr rts = do_GetService(NS_JSRT_CTRID); JSRuntime *rt; rts->GetRuntime (&rt); jsds->OnForRuntime(rt); } } else if (!nsCRT::strcmp(aTopic, NS_LITERAL_STRING(APPSTART_CATEGORY).get())) { /* on app-start, register an interest in hearing when the profile is * loaded. */ nsCOMPtr observerService = do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; if (observerService) { rv = observerService->AddObserver(this, NS_LITERAL_STRING(PROFILE_CHANGE_EVENT).get()); if (NS_FAILED(rv)) return rv; } } return NS_OK; } NS_GENERIC_FACTORY_CONSTRUCTOR(jsdASObserver); static NS_METHOD RegisterASObserver (nsIComponentManager *aCompMgr, nsIFile *aPath, const char *registryLocation, const char *componentType, const nsModuleComponentInfo *info) { nsresult rv; nsCOMPtr categoryManager(do_GetService(NS_CATMAN_CTRID, &rv)); if (NS_SUCCEEDED(rv)) { rv = categoryManager->AddCategoryEntry(APPSTART_CATEGORY, "JSDebugger app-start observer", jsdASObserverContractID, PR_TRUE, PR_TRUE,nsnull); } return rv; } static NS_METHOD UnRegisterASObserver(nsIComponentManager *aCompMgr, nsIFile *aPath, const char *registryLocation, const nsModuleComponentInfo *info) { nsresult rv; nsCOMPtr categoryManager(do_GetService(NS_CATMAN_CTRID, &rv)); if (NS_SUCCEEDED(rv)) { rv = categoryManager->DeleteCategoryEntry(APPSTART_CATEGORY, "JSDebugger app-start observer", PR_TRUE); } return rv; } static nsModuleComponentInfo components[] = { {"JSDService", JSDSERVICE_CID, jsdServiceContractID, jsdServiceConstructor}, {"JSDASObserver", JSDASO_CID, jsdASObserverContractID, jsdASObserverConstructor, RegisterASObserver, UnRegisterASObserver } }; NS_IMPL_NSGETMODULE(JavaScript_Debugger, components); /******************************************************************************** ******************************************************************************** * graveyard */ #if 0 /* Thread States */ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdThreadState, jsdIThreadState); NS_IMETHODIMP jsdThreadState::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdThreadState::GetJSDThreadState(JSDThreadState **_rval) { *_rval = mThreadState; return NS_OK; } NS_IMETHODIMP jsdThreadState::GetFrameCount (PRUint32 *_rval) { *_rval = JSD_GetCountOfStackFrames (mCx, mThreadState); return NS_OK; } NS_IMETHODIMP jsdThreadState::GetTopFrame (jsdIStackFrame **_rval) { JSDStackFrameInfo *sfi = JSD_GetStackFrame (mCx, mThreadState); *_rval = jsdStackFrame::FromPtr (mCx, mThreadState, sfi); return NS_OK; } NS_IMETHODIMP jsdThreadState::GetPendingException(jsdIValue **_rval) { JSDValue *jsdv = JSD_GetException (mCx, mThreadState); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdThreadState::SetPendingException(jsdIValue *aException) { JSDValue *jsdv; nsresult rv = aException->GetJSDValue (&jsdv); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; if (!JSD_SetException (mCx, mThreadState, jsdv)) return NS_ERROR_FAILURE; return NS_OK; } #endif