/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** 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.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert Ginda, * * 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 ***** */ #include "jsdbgapi.h" #include "jslock.h" #include "jsd_xpc.h" #include "nsIXPConnect.h" #include "mozilla/ModuleUtils.h" #include "nsIServiceManager.h" #include "nsIScriptGlobalObject.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsICategoryManager.h" #include "nsIJSRuntimeService.h" #include "nsIThreadInternal.h" #include "nsThreadUtils.h" #include "nsMemory.h" #include "jsdebug.h" #include "nsReadableUtils.h" #include "nsCRT.h" /* XXX DOM dependency */ #include "nsIScriptContext.h" #include "nsIJSContextStack.h" /* XXX private JS headers. */ #include "jscompartment.h" /* * defining CAUTIOUS_SCRIPTHOOK makes jsds disable GC while calling out to the * script hook. This was a hack to avoid some js engine problems that should * be fixed now (see Mozilla bug 77636). */ #undef CAUTIOUS_SCRIPTHOOK #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_EPHEMERAL { 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 \ { /* 2fd6b7f6-eb8c-4f32-ad26-113f2c02d0fe */ \ 0x2fd6b7f6, \ 0xeb8c, \ 0x4f32, \ {0xad, 0x26, 0x11, 0x3f, 0x2c, 0x02, 0xd0, 0xfe} \ } #define JSDS_MAJOR_VERSION 1 #define JSDS_MINOR_VERSION 2 #define NS_CATMAN_CTRID "@mozilla.org/categorymanager;1" #define NS_JSRT_CTRID "@mozilla.org/js/xpc/RuntimeService;1" #define AUTOREG_CATEGORY "xpcom-autoregistration" #define APPSTART_CATEGORY "app-startup" #define JSD_AUTOREG_ENTRY "JSDebugger Startup Observer" #define JSD_STARTUP_ENTRY "JSDebugger Startup Observer" static JSBool jsds_GCCallbackProc (JSContext *cx, JSGCStatus status); /******************************************************************************* * global vars ******************************************************************************/ const char implementationString[] = "Mozilla JavaScript Debugger Service"; const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1"; const char jsdARObserverCtrID[] = "@mozilla.org/js/jsd/app-start-observer;2"; const char jsdASObserverCtrID[] = "service,@mozilla.org/js/jsd/app-start-observer;2"; #ifdef DEBUG_verbose PRUint32 gScriptCount = 0; PRUint32 gValueCount = 0; PRUint32 gPropertyCount = 0; PRUint32 gContextCount = 0; PRUint32 gFrameCount = 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; enum PatternType { ptIgnore = 0U, ptStartsWith = 1U, ptEndsWith = 2U, ptContains = 3U, ptEquals = 4U }; static struct FilterRecord { PRCList links; jsdIFilter *filterObject; void *glob; nsCString urlPattern; PatternType patternType; PRUint32 startLine; PRUint32 endLine; } *gFilters = nsnull; static struct LiveEphemeral *gLiveValues = nsnull; static struct LiveEphemeral *gLiveProperties = nsnull; static struct LiveEphemeral *gLiveContexts = nsnull; static struct LiveEphemeral *gLiveStackFrames = nsnull; /******************************************************************************* * utility functions for ephemeral lists *******************************************************************************/ already_AddRefed jsds_FindEphemeral (LiveEphemeral **listHead, void *key) { if (!*listHead) return nsnull; LiveEphemeral *lv_record = reinterpret_cast (PR_NEXT_LINK(&(*listHead)->links)); do { if (lv_record->key == key) { NS_IF_ADDREF(lv_record->value); return lv_record->value; } lv_record = reinterpret_cast (PR_NEXT_LINK(&lv_record->links)); } while (lv_record != *listHead); return nsnull; } void jsds_InvalidateAllEphemerals (LiveEphemeral **listHead) { LiveEphemeral *lv_record = reinterpret_cast (PR_NEXT_LINK(&(*listHead)->links)); do { LiveEphemeral *next = reinterpret_cast (PR_NEXT_LINK(&lv_record->links)); lv_record->value->Invalidate(); lv_record = next; } while (*listHead); } 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 = reinterpret_cast (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); } /******************************************************************************* * utility functions for filters *******************************************************************************/ void jsds_FreeFilter (FilterRecord *rec) { NS_IF_RELEASE (rec->filterObject); PR_Free (rec); } /* copies appropriate |filter| attributes into |rec|. * False return indicates failure, the contents of |rec| will not be changed. */ PRBool jsds_SyncFilter (FilterRecord *rec, jsdIFilter *filter) { NS_ASSERTION (rec, "jsds_SyncFilter without rec"); NS_ASSERTION (filter, "jsds_SyncFilter without filter"); JSObject *glob_proper = nsnull; nsCOMPtr glob; nsresult rv = filter->GetGlobalObject(getter_AddRefs(glob)); if (NS_FAILED(rv)) return PR_FALSE; if (glob) { nsCOMPtr nsiglob = do_QueryInterface(glob); if (nsiglob) glob_proper = nsiglob->GetGlobalJSObject(); } PRUint32 startLine; rv = filter->GetStartLine(&startLine); if (NS_FAILED(rv)) return PR_FALSE; PRUint32 endLine; rv = filter->GetStartLine(&endLine); if (NS_FAILED(rv)) return PR_FALSE; nsCAutoString urlPattern; rv = filter->GetUrlPattern (urlPattern); if (NS_FAILED(rv)) return PR_FALSE; PRUint32 len = urlPattern.Length(); if (len) { if (urlPattern[0] == '*') { /* pattern starts with a *, shift all chars once to the left, * including the trailing null. */ urlPattern = Substring(urlPattern, 1, len); if (urlPattern[len - 2] == '*') { /* pattern is in the format "*foo*", overwrite the final * with * a null. */ urlPattern.Truncate(len - 2); rec->patternType = ptContains; } else { /* pattern is in the format "*foo", just make a note of the * new length. */ rec->patternType = ptEndsWith; } } else if (urlPattern[len - 1] == '*') { /* pattern is in the format "foo*", overwrite the final * with a * null. */ urlPattern.Truncate(len - 1); rec->patternType = ptStartsWith; } else { /* pattern is in the format "foo". */ rec->patternType = ptEquals; } } else { rec->patternType = ptIgnore; } /* we got everything we need without failing, now copy it into rec. */ if (rec->filterObject != filter) { NS_IF_RELEASE(rec->filterObject); NS_ADDREF(filter); rec->filterObject = filter; } rec->glob = glob_proper; rec->startLine = startLine; rec->endLine = endLine; rec->urlPattern = urlPattern; return PR_TRUE; } FilterRecord * jsds_FindFilter (jsdIFilter *filter) { if (!gFilters) return nsnull; FilterRecord *current = gFilters; do { if (current->filterObject == filter) return current; current = reinterpret_cast (PR_NEXT_LINK(¤t->links)); } while (current != gFilters); return nsnull; } /* returns true if the hook should be executed. */ PRBool jsds_FilterHook (JSDContext *jsdc, JSDThreadState *state) { JSContext *cx = JSD_GetJSContext (jsdc, state); void *glob = static_cast(JS_GetGlobalObject (cx)); if (!glob) { NS_WARNING("No global in threadstate"); return PR_FALSE; } JSDStackFrameInfo *frame = JSD_GetStackFrame (jsdc, state); if (!frame) { NS_WARNING("No frame in threadstate"); return PR_FALSE; } JSDScript *script = JSD_GetScriptForStackFrame (jsdc, state, frame); if (!script) return PR_TRUE; jsuword pc = JSD_GetPCForStackFrame (jsdc, state, frame); nsDependentCString url(JSD_GetScriptFilename (jsdc, script)); if (url.IsEmpty()) { NS_WARNING ("Script with no filename"); return PR_FALSE; } if (!gFilters) return PR_TRUE; PRUint32 currentLine = JSD_GetClosestLine (jsdc, script, pc); PRUint32 len = 0; FilterRecord *currentFilter = gFilters; do { PRUint32 flags = 0; nsresult rv = currentFilter->filterObject->GetFlags(&flags); NS_ASSERTION(NS_SUCCEEDED(rv), "Error getting flags for filter"); if (flags & jsdIFilter::FLAG_ENABLED) { /* if there is no glob, or the globs match */ if ((!currentFilter->glob || currentFilter->glob == glob) && /* and there is no start line, or the start line is before * or equal to the current */ (!currentFilter->startLine || currentFilter->startLine <= currentLine) && /* and there is no end line, or the end line is after * or equal to the current */ (!currentFilter->endLine || currentFilter->endLine >= currentLine)) { /* then we're going to have to compare the url. */ if (currentFilter->patternType == ptIgnore) return !!(flags & jsdIFilter::FLAG_PASS); if (!len) len = url.Length(); nsCString urlPattern = currentFilter->urlPattern; PRUint32 patternLength = urlPattern.Length(); if (len >= patternLength) { switch (currentFilter->patternType) { case ptEquals: if (urlPattern.Equals(url)) return !!(flags & jsdIFilter::FLAG_PASS); break; case ptStartsWith: if (urlPattern.Equals(Substring(url, 0, patternLength))) return !!(flags & jsdIFilter::FLAG_PASS); break; case ptEndsWith: if (urlPattern.Equals(Substring(url, len - patternLength))) return !!(flags & jsdIFilter::FLAG_PASS); break; case ptContains: { nsACString::const_iterator start, end; url.BeginReading(start); url.EndReading(end); if (FindInReadable(currentFilter->urlPattern, start, end)) return !!(flags & jsdIFilter::FLAG_PASS); } break; default: NS_ERROR("Invalid pattern type"); } } } } currentFilter = reinterpret_cast (PR_NEXT_LINK(¤tFilter->links)); } while (currentFilter != gFilters); return PR_TRUE; } /******************************************************************************* * c callbacks *******************************************************************************/ static void jsds_NotifyPendingDeadScripts (JSContext *cx) { #ifdef CAUTIOUS_SCRIPTHOOK JSRuntime *rt = JS_GetRuntime(cx); #endif jsdService *jsds = gJsds; nsCOMPtr hook; if (jsds) { NS_ADDREF(jsds); jsds->GetScriptHook (getter_AddRefs(hook)); jsds->Pause(nsnull); } DeadScript *deadScripts = gDeadScripts; gDeadScripts = nsnull; while (deadScripts) { DeadScript *ds = deadScripts; /* get next deleted script */ deadScripts = reinterpret_cast (PR_NEXT_LINK(&ds->links)); if (deadScripts == ds) deadScripts = nsnull; if (hook) { /* tell the user this script has been destroyed */ #ifdef CAUTIOUS_SCRIPTHOOK JS_UNKEEP_ATOMS(rt); #endif hook->OnScriptDestroyed (ds->script); #ifdef CAUTIOUS_SCRIPTHOOK JS_KEEP_ATOMS(rt); #endif } /* take it 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); } if (jsds) { jsds->UnPause(nsnull); NS_RELEASE(jsds); } } static JSBool jsds_GCCallbackProc (JSContext *cx, JSGCStatus status) { #ifdef DEBUG_verbose printf ("new gc status is %i\n", status); #endif if (status == JSGC_END) { /* just to guard against reentering. */ gGCStatus = JSGC_BEGIN; while (gDeadScripts) jsds_NotifyPendingDeadScripts (cx); } gGCStatus = status; if (gLastGCProc && !gLastGCProc (cx, status)) { /* * If gLastGCProc returns false, then the GC will abort without making * another callback with status=JSGC_END, so set the status to JSGC_END * here. */ gGCStatus = JSGC_END; return JS_FALSE; } return JS_TRUE; } static uintN jsds_ErrorHookProc (JSDContext *jsdc, JSContext *cx, const char *message, JSErrorReport *report, void *callerdata) { static PRBool running = PR_FALSE; nsCOMPtr hook; gJsds->GetErrorHook(getter_AddRefs(hook)); if (!hook) return JSD_ERROR_REPORTER_PASS_ALONG; if (running) return JSD_ERROR_REPORTER_PASS_ALONG; running = PR_TRUE; nsCOMPtr val; if (JS_IsExceptionPending(cx)) { jsval jv; JS_GetPendingException(cx, &jv); JSDValue *jsdv = JSD_NewValue (jsdc, jv); val = getter_AddRefs(jsdValue::FromPtr(jsdc, jsdv)); } nsCAutoString fileName; PRUint32 line; PRUint32 pos; PRUint32 flags; PRUint32 errnum; PRBool rval; if (report) { fileName.Assign(report->filename); line = report->lineno; pos = report->tokenptr - report->linebuf; flags = report->flags; errnum = report->errorNumber; } else { line = 0; pos = 0; flags = 0; errnum = 0; } gJsds->Pause(nsnull); hook->OnError (nsDependentCString(message), fileName, line, pos, flags, errnum, val, &rval); gJsds->UnPause(nsnull); running = PR_FALSE; if (!rval) return JSD_ERROR_REPORTER_DEBUG; return JSD_ERROR_REPORTER_PASS_ALONG; } static JSBool jsds_CallHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate, uintN type, void* callerdata) { nsCOMPtr hook; switch (type) { case JSD_HOOK_TOPLEVEL_START: case JSD_HOOK_TOPLEVEL_END: gJsds->GetTopLevelHook(getter_AddRefs(hook)); break; case JSD_HOOK_FUNCTION_CALL: case JSD_HOOK_FUNCTION_RETURN: gJsds->GetFunctionHook(getter_AddRefs(hook)); break; default: NS_ASSERTION (0, "Unknown hook type."); } if (!hook) return JS_TRUE; if (!jsds_FilterHook (jsdc, jsdthreadstate)) return JS_FALSE; JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate); nsCOMPtr frame = getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, native_frame)); gJsds->Pause(nsnull); hook->OnCall(frame, type); gJsds->UnPause(nsnull); jsdStackFrame::InvalidateAll(); 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; nsCOMPtr js_rv; switch (type) { case JSD_HOOK_INTERRUPTED: gJsds->GetInterruptHook(getter_AddRefs(hook)); break; case JSD_HOOK_DEBUG_REQUESTED: gJsds->GetDebugHook(getter_AddRefs(hook)); break; case JSD_HOOK_DEBUGGER_KEYWORD: gJsds->GetDebuggerHook(getter_AddRefs(hook)); break; case JSD_HOOK_BREAKPOINT: { /* we can't pause breakpoints the way we pause the other * execution hooks (at least, not easily.) Instead we bail * here if the service is paused. */ PRUint32 level; gJsds->GetPauseDepth(&level); if (!level) gJsds->GetBreakpointHook(getter_AddRefs(hook)); } break; case JSD_HOOK_THROW: { hook_rv = JSD_HOOK_RETURN_CONTINUE_THROW; gJsds->GetThrowHook(getter_AddRefs(hook)); if (hook) { JSDValue *jsdv = JSD_GetException (jsdc, jsdthreadstate); js_rv = getter_AddRefs(jsdValue::FromPtr (jsdc, jsdv)); } break; } default: NS_ASSERTION (0, "Unknown hook type."); } if (!hook) return hook_rv; if (!jsds_FilterHook (jsdc, jsdthreadstate)) return JSD_HOOK_RETURN_CONTINUE; JSDStackFrameInfo *native_frame = JSD_GetStackFrame (jsdc, jsdthreadstate); nsCOMPtr frame = getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, native_frame)); gJsds->Pause(nsnull); 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); 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))) *rval = JSD_GetValueWrappedJSVal(jsdc, jsdv); } } return hook_rv; } static void jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, void* callerdata) { #ifdef CAUTIOUS_SCRIPTHOOK JSContext *cx = JSD_GetDefaultJSContext(jsdc); JSRuntime *rt = JS_GetRuntime(cx); #endif if (creating) { nsCOMPtr hook; gJsds->GetScriptHook(getter_AddRefs(hook)); /* a script is being created */ if (!hook) { /* nobody cares, just exit */ return; } nsCOMPtr script = getter_AddRefs(jsdScript::FromPtr(jsdc, jsdscript)); #ifdef CAUTIOUS_SCRIPTHOOK JS_UNKEEP_ATOMS(rt); #endif gJsds->Pause(nsnull); hook->OnScriptCreated (script); gJsds->UnPause(nsnull); #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 jsdis = static_cast(JSD_GetScriptPrivate(jsdscript)); if (!jsdis) return; jsdis->Invalidate(); if (gGCStatus == JSGC_END) { nsCOMPtr hook; gJsds->GetScriptHook(getter_AddRefs(hook)); if (!hook) 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); hook->OnScriptDestroyed (jsdis); gJsds->UnPause(nsnull); #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); if (!ds) return; /* NS_ERROR_OUT_OF_MEMORY */ ds->jsdc = jsdc; ds->script = jsdis; NS_ADDREF(ds->script); 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_ISUPPORTS2(jsdContext, jsdIContext, jsdIEphemeral); 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(nsACString &_rval) { _rval.Assign(JSD_GetObjectNewURL(mCx, mObject)); return NS_OK; } NS_IMETHODIMP jsdObject::GetCreatorLine(PRUint32 *_rval) { *_rval = JSD_GetObjectNewLineNumber(mCx, mObject); return NS_OK; } NS_IMETHODIMP jsdObject::GetConstructorURL(nsACString &_rval) { _rval.Assign(JSD_GetObjectConstructorURL(mCx, mObject)); 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; } /* 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); mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveProperties, &mLiveListEntry); } jsdProperty::~jsdProperty () { DEBUG_DESTROY ("jsdProperty", gPropertyCount); if (mValid) Invalidate(); } NS_IMETHODIMP jsdProperty::Invalidate() { ASSERT_VALID_EPHEMERAL; 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) static NS_IMETHODIMP AssignToJSString(nsACString *x, JSString *str) { if (!str) { x->SetLength(0); return NS_OK; } size_t length = JS_GetStringEncodingLength(NULL, str); if (length == size_t(-1)) return NS_ERROR_FAILURE; x->SetLength(PRUint32(length)); if (x->Length() != PRUint32(length)) return NS_ERROR_OUT_OF_MEMORY; JS_EncodeStringToBuffer(str, x->BeginWriting(), length); return NS_OK; } jsdScript::jsdScript (JSDContext *aCx, JSDScript *aScript) : mValid(PR_FALSE), mTag(0), mCx(aCx), mScript(aScript), mFileName(0), mFunctionName(0), mBaseLineNumber(0), mLineExtent(0), mPPLineMap(0), mFirstPC(0) { DEBUG_CREATE ("jsdScript", gScriptCount); 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(); if (mFunctionName) { JSString *str = JSD_GetScriptFunctionId(mCx, mScript); if (str) AssignToJSString(mFunctionName, str); } mBaseLineNumber = JSD_GetScriptBaseLineNumber(mCx, mScript); mLineExtent = JSD_GetScriptLineExtent(mCx, mScript); mFirstPC = JSD_GetClosestPC(mCx, mScript, 0); JSD_UnlockScriptSubsystem(mCx); mValid = PR_TRUE; } } jsdScript::~jsdScript () { DEBUG_DESTROY ("jsdScript", gScriptCount); if (mFileName) delete mFileName; if (mFunctionName) delete mFunctionName; if (mPPLineMap) PR_Free(mPPLineMap); /* 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."); } /* * This method populates a line <-> pc map for a pretty printed version of this * script. It does this by decompiling, and then recompiling the script. The * resulting script is scanned for the line map, and then left as GC fodder. */ PCMapEntry * jsdScript::CreatePPLineMap() { JSContext *cx = JSD_GetDefaultJSContext (mCx); JSAutoRequest ar(cx); JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL); JSFunction *fun = JSD_GetJSFunction (mCx, mScript); JSScript *script; /* In JSD compartment */ PRUint32 baseLine; PRBool scriptOwner = PR_FALSE; JSString *jsstr; size_t length; const jschar *chars; if (fun) { uintN nargs; { JSAutoEnterCompartment ac; if (!ac.enter(cx, JS_GetFunctionObject(fun))) return nsnull; nargs = JS_GetFunctionArgumentCount(cx, fun); if (nargs > 12) return nsnull; jsstr = JS_DecompileFunctionBody (cx, fun, 4); if (!jsstr) return nsnull; if (!(chars = JS_GetStringCharsAndLength(cx, jsstr, &length))) return nsnull; } JS::Anchor kungFuDeathGrip(jsstr); const char *argnames[] = {"arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8", "arg9", "arg10", "arg11", "arg12" }; fun = JS_CompileUCFunction (cx, obj, "ppfun", nargs, argnames, chars, length, "x-jsd:ppbuffer?type=function", 3); if (!fun || !(script = JS_GetFunctionScript(cx, fun))) return nsnull; baseLine = 3; } else { script = JSD_GetJSScript(mCx, mScript); JSString *jsstr; { JSAutoEnterCompartment ac; if (!ac.enter(cx, script)) return nsnull; jsstr = JS_DecompileScript (cx, JSD_GetJSScript(mCx, mScript), "ppscript", 4); if (!jsstr) return nsnull; if (!(chars = JS_GetStringCharsAndLength(cx, jsstr, &length))) return nsnull; } JS::Anchor kungFuDeathGrip(jsstr); script = JS_CompileUCScript (cx, obj, chars, length, "x-jsd:ppbuffer?type=script", 1); if (!script) return nsnull; scriptOwner = PR_TRUE; baseLine = 1; } PRUint32 scriptExtent = JS_GetScriptLineExtent (cx, script); jsbytecode* firstPC = JS_LineNumberToPC (cx, script, 0); /* allocate worst case size of map (number of lines in script + 1 * for our 0 record), we'll shrink it with a realloc later. */ PCMapEntry *lineMap = static_cast (PR_Malloc((scriptExtent + 1) * sizeof (PCMapEntry))); PRUint32 lineMapSize = 0; if (lineMap) { for (PRUint32 line = baseLine; line < scriptExtent + baseLine; ++line) { jsbytecode* pc = JS_LineNumberToPC (cx, script, line); if (line == JS_PCToLineNumber (cx, script, pc)) { lineMap[lineMapSize].line = line; lineMap[lineMapSize].pc = pc - firstPC; ++lineMapSize; } } if (scriptExtent != lineMapSize) { lineMap = static_cast (PR_Realloc(mPPLineMap = lineMap, lineMapSize * sizeof(PCMapEntry))); if (!lineMap) { PR_Free(mPPLineMap); lineMapSize = 0; } } } if (scriptOwner) JS_DestroyScript (cx, script); mPCMapSize = lineMapSize; return mPPLineMap = lineMap; } PRUint32 jsdScript::PPPcToLine (PRUint32 aPC) { if (!mPPLineMap && !CreatePPLineMap()) return 0; PRUint32 i; for (i = 1; i < mPCMapSize; ++i) { if (mPPLineMap[i].pc > aPC) return mPPLineMap[i - 1].line; } return mPPLineMap[mPCMapSize - 1].line; } PRUint32 jsdScript::PPLineToPc (PRUint32 aLine) { if (!mPPLineMap && !CreatePPLineMap()) return 0; PRUint32 i; for (i = 1; i < mPCMapSize; ++i) { if (mPPLineMap[i].line > aLine) return mPPLineMap[i - 1].pc; } return mPPLineMap[mPCMapSize - 1].pc; } NS_IMETHODIMP jsdScript::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdScript::GetJSDScript(JSDScript **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mScript; return NS_OK; } NS_IMETHODIMP jsdScript::GetVersion (PRInt32 *_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); JSScript *script = JSD_GetJSScript(mCx, mScript); JSAutoEnterCompartment ac; if (!ac.enter(cx, script)) return NS_ERROR_FAILURE; *_rval = static_cast(JS_GetScriptVersion(cx, script)); return NS_OK; } NS_IMETHODIMP jsdScript::GetTag(PRUint32 *_rval) { if (!mTag) mTag = ++jsdScript::LastTag; *_rval = mTag; return NS_OK; } NS_IMETHODIMP jsdScript::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = PR_FALSE; /* release the addref we do in FromPtr */ jsdIScript *script = static_cast (JSD_GetScriptPrivate(mScript)); NS_ASSERTION (script == this, "That's not my script!"); NS_RELEASE(script); JSD_SetScriptPrivate(mScript, NULL); return NS_OK; } void jsdScript::InvalidateAll () { JSDContext *cx; if (NS_FAILED(gJsds->GetJSDContext (&cx))) return; JSDScript *script; JSDScript *iter = NULL; JSD_LockScriptSubsystem(cx); while((script = JSD_IterateScripts(cx, &iter)) != NULL) { nsCOMPtr jsdis = static_cast(JSD_GetScriptPrivate(script)); if (jsdis) jsdis->Invalidate(); } JSD_UnlockScriptSubsystem(cx); } NS_IMETHODIMP jsdScript::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdScript::SetFlags(PRUint32 flags) { ASSERT_VALID_EPHEMERAL; JSD_SetScriptFlags(mCx, mScript, flags); return NS_OK; } NS_IMETHODIMP jsdScript::GetFlags(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptFlags(mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetFileName(nsACString &_rval) { _rval.Assign(*mFileName); return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionName(nsACString &_rval) { _rval.Assign(*mFunctionName); return NS_OK; } NS_IMETHODIMP jsdScript::GetParameterNames(PRUint32* count, PRUnichar*** paramNames) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!cx) { NS_WARNING("No default context !?"); return NS_ERROR_FAILURE; } JSFunction *fun = JSD_GetJSFunction (mCx, mScript); if (!fun) { *count = 0; *paramNames = nsnull; return NS_OK; } JSAutoRequest ar(cx); JSAutoEnterCompartment ac; if (!ac.enter(cx, JS_GetFunctionObject(fun))) return NS_ERROR_FAILURE; uintN nargs; if (!JS_FunctionHasLocalNames(cx, fun) || (nargs = JS_GetFunctionArgumentCount(cx, fun)) == 0) { *count = 0; *paramNames = nsnull; return NS_OK; } PRUnichar **ret = static_cast(NS_Alloc(nargs * sizeof(PRUnichar*))); if (!ret) return NS_ERROR_OUT_OF_MEMORY; void *mark; jsuword *names = JS_GetFunctionLocalNameArray(cx, fun, &mark); if (!names) { NS_Free(ret); return NS_ERROR_OUT_OF_MEMORY; } nsresult rv = NS_OK; for (uintN i = 0; i < nargs; ++i) { JSAtom *atom = JS_LocalNameToAtom(names[i]); if (!atom) { ret[i] = 0; } else { JSString *str = JS_AtomKey(atom); ret[i] = NS_strndup(JS_GetInternedStringChars(str), JS_GetStringLength(str)); if (!ret[i]) { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret); rv = NS_ERROR_OUT_OF_MEMORY; break; } } } JS_ReleaseFunctionLocalNameArray(cx, mark); if (NS_FAILED(rv)) return rv; *count = nargs; *paramNames = ret; return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionObject(jsdIValue **_rval) { JSFunction *fun = JSD_GetJSFunction(mCx, mScript); if (!fun) return NS_ERROR_NOT_AVAILABLE; JSObject *obj = JS_GetFunctionObject(fun); if (!obj) return NS_ERROR_FAILURE; JSDContext *cx; if (NS_FAILED(gJsds->GetJSDContext (&cx))) return NS_ERROR_NOT_INITIALIZED; JSDValue *jsdv = JSD_NewValue(cx, OBJECT_TO_JSVAL(obj)); if (!jsdv) return NS_ERROR_OUT_OF_MEMORY; *_rval = jsdValue::FromPtr(cx, jsdv); if (!*_rval) { JSD_DropValue(cx, jsdv); return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } NS_IMETHODIMP jsdScript::GetFunctionSource(nsAString & aFunctionSource) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!cx) { NS_WARNING("No default context !?"); return NS_ERROR_FAILURE; } JSFunction *fun = JSD_GetJSFunction (mCx, mScript); JSAutoRequest ar(cx); JSString *jsstr; JSAutoEnterCompartment ac; if (fun) { if (!ac.enter(cx, JS_GetFunctionObject(fun))) return NS_ERROR_FAILURE; jsstr = JS_DecompileFunction (cx, fun, 4); } else { JSScript *script = JSD_GetJSScript (mCx, mScript); if (!ac.enter(cx, script)) return NS_ERROR_FAILURE; jsstr = JS_DecompileScript (cx, script, "ppscript", 4); } if (!jsstr) return NS_ERROR_FAILURE; size_t length; const jschar *chars = JS_GetStringCharsZAndLength(cx, jsstr, &length); if (!chars) return NS_ERROR_FAILURE; aFunctionSource = nsDependentString(chars, length); 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::GetCallCount(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptCallCount (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMaxRecurseDepth(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMaxRecurseDepth (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMinExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMinExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMaxExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMaxExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetTotalExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptTotalExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMinOwnExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMinOwnExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetMaxOwnExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptMaxOwnExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::GetTotalOwnExecutionTime(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetScriptTotalOwnExecutionTime (mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::ClearProfileData() { ASSERT_VALID_EPHEMERAL; JSD_ClearScriptProfileData(mCx, mScript); return NS_OK; } NS_IMETHODIMP jsdScript::PcToLine(PRUint32 aPC, PRUint32 aPcmap, PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { *_rval = JSD_GetClosestLine (mCx, mScript, mFirstPC + aPC); } else if (aPcmap == PCMAP_PRETTYPRINT) { *_rval = PPPcToLine(aPC); } else { return NS_ERROR_INVALID_ARG; } return NS_OK; } NS_IMETHODIMP jsdScript::LineToPc(PRUint32 aLine, PRUint32 aPcmap, PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); *_rval = pc - mFirstPC; } else if (aPcmap == PCMAP_PRETTYPRINT) { *_rval = PPLineToPc(aLine); } else { return NS_ERROR_INVALID_ARG; } return NS_OK; } NS_IMETHODIMP jsdScript::EnableSingleStepInterrupts(PRBool enable) { ASSERT_VALID_EPHEMERAL; /* Must have set interrupt hook before enabling */ if (enable && !jsdService::GetService()->CheckInterruptHook()) return NS_ERROR_NOT_INITIALIZED; return (JSD_EnableSingleStepInterrupts(mCx, mScript, enable) ? NS_OK : NS_ERROR_FAILURE); } NS_IMETHODIMP jsdScript::IsLineExecutable(PRUint32 aLine, PRUint32 aPcmap, PRBool *_rval) { ASSERT_VALID_EPHEMERAL; if (aPcmap == PCMAP_SOURCETEXT) { jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine); *_rval = (aLine == JSD_GetClosestLine (mCx, mScript, pc)); } else if (aPcmap == PCMAP_PRETTYPRINT) { if (!mPPLineMap && !CreatePPLineMap()) return NS_ERROR_OUT_OF_MEMORY; *_rval = PR_FALSE; for (PRUint32 i = 0; i < mPCMapSize; ++i) { if (mPPLineMap[i].line >= aLine) { *_rval = (mPPLineMap[i].line == aLine); break; } } } else { return NS_ERROR_INVALID_ARG; } return NS_OK; } NS_IMETHODIMP jsdScript::SetBreakpoint(PRUint32 aPC) { ASSERT_VALID_EPHEMERAL; jsuword pc = mFirstPC + aPC; JSD_SetExecutionHook (mCx, mScript, pc, jsds_ExecutionHookProc, NULL); return NS_OK; } NS_IMETHODIMP jsdScript::ClearBreakpoint(PRUint32 aPC) { ASSERT_VALID_EPHEMERAL; jsuword pc = mFirstPC + aPC; JSD_ClearExecutionHook (mCx, mScript, pc); return NS_OK; } NS_IMETHODIMP jsdScript::ClearAllBreakpoints() { ASSERT_VALID_EPHEMERAL; JSD_LockScriptSubsystem(mCx); JSD_ClearAllExecutionHooksForScript (mCx, mScript); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } /* Contexts */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdContext, jsdIContext, jsdIEphemeral) jsdIContext * jsdContext::FromPtr (JSDContext *aJSDCx, JSContext *aJSCx) { if (!aJSDCx || !aJSCx) return nsnull; nsCOMPtr jsdicx; nsCOMPtr eph = jsds_FindEphemeral (&gLiveContexts, static_cast(aJSCx)); if (eph) { jsdicx = do_QueryInterface(eph); } else { nsCOMPtr iscx; if (JS_GetOptions(aJSCx) & JSOPTION_PRIVATE_IS_NSISUPPORTS) iscx = static_cast(JS_GetContextPrivate(aJSCx)); jsdicx = new jsdContext (aJSDCx, aJSCx, iscx); } jsdIContext *ctx = nsnull; jsdicx.swap(ctx); return ctx; } jsdContext::jsdContext (JSDContext *aJSDCx, JSContext *aJSCx, nsISupports *aISCx) : mValid(PR_TRUE), mTag(0), mJSDCx(aJSDCx), mJSCx(aJSCx), mISCx(aISCx) { DEBUG_CREATE ("jsdContext", gContextCount); mLiveListEntry.value = this; mLiveListEntry.key = static_cast(aJSCx); jsds_InsertEphemeral (&gLiveContexts, &mLiveListEntry); } jsdContext::~jsdContext() { DEBUG_DESTROY ("jsdContext", gContextCount); if (mValid) { /* call Invalidate() to take ourselves out of the live list */ Invalidate(); } } NS_IMETHODIMP jsdContext::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdContext::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = PR_FALSE; jsds_RemoveEphemeral (&gLiveContexts, &mLiveListEntry); return NS_OK; } void jsdContext::InvalidateAll() { if (gLiveContexts) jsds_InvalidateAllEphemerals (&gLiveContexts); } NS_IMETHODIMP jsdContext::GetJSContext(JSContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mJSCx; return NS_OK; } NS_IMETHODIMP jsdContext::GetOptions(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JS_GetOptions(mJSCx); return NS_OK; } NS_IMETHODIMP jsdContext::SetOptions(PRUint32 options) { ASSERT_VALID_EPHEMERAL; PRUint32 lastOptions = JS_GetOptions(mJSCx); /* don't let users change this option, they'd just be shooting themselves * in the foot. */ if ((options ^ lastOptions) & JSOPTION_PRIVATE_IS_NSISUPPORTS) return NS_ERROR_ILLEGAL_VALUE; JS_SetOptions(mJSCx, options); return NS_OK; } NS_IMETHODIMP jsdContext::GetPrivateData(nsISupports **_rval) { ASSERT_VALID_EPHEMERAL; PRUint32 options = JS_GetOptions(mJSCx); if (options & JSOPTION_PRIVATE_IS_NSISUPPORTS) { *_rval = static_cast(JS_GetContextPrivate(mJSCx)); NS_IF_ADDREF(*_rval); } else { *_rval = nsnull; } return NS_OK; } NS_IMETHODIMP jsdContext::GetWrappedContext(nsISupports **_rval) { ASSERT_VALID_EPHEMERAL; NS_IF_ADDREF(*_rval = mISCx); return NS_OK; } NS_IMETHODIMP jsdContext::GetTag(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; if (!mTag) mTag = ++jsdContext::LastTag; *_rval = mTag; return NS_OK; } NS_IMETHODIMP jsdContext::GetVersion (PRInt32 *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = static_cast(JS_GetVersion(mJSCx)); return NS_OK; } NS_IMETHODIMP jsdContext::SetVersion (PRInt32 id) { ASSERT_VALID_EPHEMERAL; JSVersion ver = static_cast(id); JS_SetVersion(mJSCx, ver); return NS_OK; } NS_IMETHODIMP jsdContext::GetGlobalObject (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSObject *glob = JS_GetGlobalObject(mJSCx); JSDValue *jsdv = JSD_NewValue (mJSDCx, OBJECT_TO_JSVAL(glob)); if (!jsdv) return NS_ERROR_FAILURE; *_rval = jsdValue::FromPtr (mJSDCx, jsdv); if (!*_rval) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP jsdContext::GetScriptsEnabled (PRBool *_rval) { ASSERT_VALID_EPHEMERAL; if (!mISCx) { *_rval = PR_TRUE; return NS_OK; } nsCOMPtr context = do_QueryInterface(mISCx); if (!context) return NS_ERROR_NO_INTERFACE; *_rval = context->GetScriptsEnabled(); return NS_OK; } NS_IMETHODIMP jsdContext::SetScriptsEnabled (PRBool _rval) { ASSERT_VALID_EPHEMERAL; if (!mISCx) { if (_rval) return NS_OK; return NS_ERROR_NO_INTERFACE; } nsCOMPtr context = do_QueryInterface(mISCx); if (!context) return NS_ERROR_NO_INTERFACE; context->SetScriptsEnabled(_rval, PR_TRUE); return NS_OK; } /* Stack Frames */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdStackFrame, jsdIStackFrame, jsdIEphemeral) jsdStackFrame::jsdStackFrame (JSDContext *aCx, JSDThreadState *aThreadState, JSDStackFrameInfo *aStackFrameInfo) : mCx(aCx), mThreadState(aThreadState), mStackFrameInfo(aStackFrameInfo) { DEBUG_CREATE ("jsdStackFrame", gFrameCount); mValid = (aCx && aThreadState && aStackFrameInfo); if (mValid) { mLiveListEntry.key = aStackFrameInfo; mLiveListEntry.value = this; jsds_InsertEphemeral (&gLiveStackFrames, &mLiveListEntry); } } jsdStackFrame::~jsdStackFrame() { DEBUG_DESTROY ("jsdStackFrame", gFrameCount); if (mValid) { /* call Invalidate() to take ourselves out of the live list */ Invalidate(); } } jsdIStackFrame * jsdStackFrame::FromPtr (JSDContext *aCx, JSDThreadState *aThreadState, JSDStackFrameInfo *aStackFrameInfo) { if (!aStackFrameInfo) return nsnull; jsdIStackFrame *rv; nsCOMPtr frame; nsCOMPtr eph = jsds_FindEphemeral (&gLiveStackFrames, reinterpret_cast(aStackFrameInfo)); if (eph) { frame = do_QueryInterface(eph); rv = frame; } else { rv = new jsdStackFrame (aCx, aThreadState, aStackFrameInfo); } NS_IF_ADDREF(rv); return rv; } NS_IMETHODIMP jsdStackFrame::Invalidate() { ASSERT_VALID_EPHEMERAL; mValid = PR_FALSE; jsds_RemoveEphemeral (&gLiveStackFrames, &mLiveListEntry); return NS_OK; } void jsdStackFrame::InvalidateAll() { if (gLiveStackFrames) jsds_InvalidateAllEphemerals (&gLiveStackFrames); } NS_IMETHODIMP jsdStackFrame::GetJSDContext(JSDContext **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetJSDThreadState(JSDThreadState **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mThreadState; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetJSDStackFrameInfo(JSDStackFrameInfo **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mStackFrameInfo; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsValid(PRBool *_rval) { *_rval = mValid; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetCallingFrame(jsdIStackFrame **_rval) { ASSERT_VALID_EPHEMERAL; JSDStackFrameInfo *sfi = JSD_GetCallingStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdStackFrame::FromPtr (mCx, mThreadState, sfi); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetExecutionContext(jsdIContext **_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetJSContext (mCx, mThreadState); *_rval = jsdContext::FromPtr (mCx, cx); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetFunctionName(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; JSString *str = JSD_GetIdForStackFrame(mCx, mThreadState, mStackFrameInfo); if (str) return AssignToJSString(&_rval, str); _rval.Assign("anonymous"); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsDebugger(PRBool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsStackFrameDebugger (mCx, mThreadState, mStackFrameInfo); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetIsConstructing(PRBool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsStackFrameConstructing (mCx, mThreadState, mStackFrameInfo); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetScript(jsdIScript **_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdScript::FromPtr (mCx, script); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetPc(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); if (!script) return NS_ERROR_FAILURE; jsuword pcbase = JSD_GetClosestPC(mCx, script, 0); jsuword pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); if (pc) *_rval = pc - pcbase; else *_rval = pcbase; return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetLine(PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, mStackFrameInfo); if (script) { jsuword pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = JSD_GetClosestLine (mCx, script, pc); } else { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetCallee(jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetCallObjectForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetScope(jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetScopeChainForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::GetThisValue(jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetThisForStackFrame (mCx, mThreadState, mStackFrameInfo); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdStackFrame::Eval (const nsAString &bytes, const nsACString &fileName, PRUint32 line, jsdIValue **result, PRBool *_rval) { ASSERT_VALID_EPHEMERAL; if (bytes.IsEmpty()) return NS_ERROR_INVALID_ARG; // get pointer to buffer contained in |bytes| nsAString::const_iterator h; bytes.BeginReading(h); const jschar *char_bytes = reinterpret_cast(h.get()); JSExceptionState *estate = 0; jsval jv; JSContext *cx = JSD_GetJSContext (mCx, mThreadState); JSAutoRequest ar(cx); estate = JS_SaveExceptionState (cx); JS_ClearPendingException (cx); nsresult rv; nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv); if (NS_SUCCEEDED(rv)) rv = stack->Push(cx); if (NS_FAILED(rv)) { JS_RestoreExceptionState (cx, estate); return rv; } *_rval = JSD_AttemptUCScriptInStackFrame (mCx, mThreadState, mStackFrameInfo, char_bytes, bytes.Length(), PromiseFlatCString(fileName).get(), line, &jv); if (!*_rval) { if (JS_IsExceptionPending(cx)) JS_GetPendingException (cx, &jv); else jv = JSVAL_NULL; } JS_RestoreExceptionState (cx, estate); #ifdef DEBUG JSContext* poppedCX; rv = stack->Pop(&poppedCX); NS_ASSERTION(NS_SUCCEEDED(rv) && poppedCX == cx, "bad pop"); #else (void) stack->Pop(nsnull); #endif JSDValue *jsdv = JSD_NewValue (mCx, jv); if (!jsdv) return NS_ERROR_FAILURE; *result = jsdValue::FromPtr (mCx, jsdv); if (!*result) return NS_ERROR_FAILURE; return NS_OK; } /* Values */ NS_IMPL_THREADSAFE_ISUPPORTS2(jsdValue, jsdIValue, jsdIEphemeral) jsdIValue * jsdValue::FromPtr (JSDContext *aCx, JSDValue *aValue) { /* value will be dropped by te jsdValue destructor. */ if (!aValue) return nsnull; jsdIValue *rv = new jsdValue (aCx, aValue); NS_IF_ADDREF(rv); return rv; } jsdValue::jsdValue (JSDContext *aCx, JSDValue *aValue) : mValid(PR_TRUE), mCx(aCx), mValue(aValue) { DEBUG_CREATE ("jsdValue", gValueCount); 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_EPHEMERAL; 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_EPHEMERAL; *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdValue::GetJSDValue (JSDValue **_rval) { ASSERT_VALID_EPHEMERAL; *_rval = mValue; return NS_OK; } NS_IMETHODIMP jsdValue::GetIsNative (PRBool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsValueNative (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIsNumber (PRBool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsValueNumber (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIsPrimitive (PRBool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_IsValuePrimitive (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsType (PRUint32 *_rval) { ASSERT_VALID_EPHEMERAL; jsval val; val = JSD_GetValueWrappedJSVal (mCx, mValue); if (JSVAL_IS_NULL(val)) *_rval = TYPE_NULL; else if (JSVAL_IS_BOOLEAN(val)) *_rval = TYPE_BOOLEAN; else if (JSVAL_IS_DOUBLE(val)) *_rval = TYPE_DOUBLE; else if (JSVAL_IS_INT(val)) *_rval = TYPE_INT; else if (JSVAL_IS_STRING(val)) *_rval = TYPE_STRING; else if (JSVAL_IS_VOID(val)) *_rval = TYPE_VOID; else if (JSD_IsValueFunction (mCx, mValue)) *_rval = TYPE_FUNCTION; else if (JSVAL_IS_OBJECT(val)) *_rval = TYPE_OBJECT; else NS_ASSERTION (0, "Value has no discernible type."); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsPrototype (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetValuePrototype (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsParent (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetValueParent (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsClassName(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; _rval.Assign(JSD_GetValueClassName(mCx, mValue)); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsConstructor (jsdIValue **_rval) { ASSERT_VALID_EPHEMERAL; JSDValue *jsdv = JSD_GetValueConstructor (mCx, mValue); *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdValue::GetJsFunctionName(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; return AssignToJSString(&_rval, JSD_GetValueFunctionId(mCx, mValue)); } NS_IMETHODIMP jsdValue::GetBooleanValue(PRBool *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetValueBoolean (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetDoubleValue(double *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetValueDouble (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetIntValue(PRInt32 *_rval) { ASSERT_VALID_EPHEMERAL; *_rval = JSD_GetValueInt (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetObjectValue(jsdIObject **_rval) { ASSERT_VALID_EPHEMERAL; JSDObject *obj; obj = JSD_GetObjectForValue (mCx, mValue); *_rval = jsdObject::FromPtr (mCx, obj); if (!*_rval) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP jsdValue::GetStringValue(nsACString &_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!cx) { NS_WARNING("No default context !?"); return NS_ERROR_FAILURE; } JSString *jstr_val = JSD_GetValueString(mCx, mValue); if (jstr_val) { size_t length; const jschar *chars = JS_GetStringCharsZAndLength(cx, jstr_val, &length); if (!chars) return NS_ERROR_FAILURE; nsDependentString depStr(chars, length); CopyUTF16toUTF8(depStr, _rval); } else { _rval.Truncate(); } return NS_OK; } NS_IMETHODIMP jsdValue::GetPropertyCount (PRInt32 *_rval) { ASSERT_VALID_EPHEMERAL; 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_EPHEMERAL; *propArray = nsnull; if (length) *length = 0; PRUint32 prop_count = JSD_IsValueObject(mCx, mValue) ? JSD_GetCountOfProperties (mCx, mValue) : 0; NS_ENSURE_TRUE(prop_count, NS_OK); jsdIProperty **pa_temp = static_cast (nsMemory::Alloc(sizeof (jsdIProperty *) * prop_count)); NS_ENSURE_TRUE(pa_temp, NS_ERROR_OUT_OF_MEMORY); 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 nsACString &name, jsdIProperty **_rval) { ASSERT_VALID_EPHEMERAL; JSContext *cx = JSD_GetDefaultJSContext (mCx); JSAutoRequest ar(cx); /* not rooting this */ JSString *jstr_name = JS_NewStringCopyZ(cx, PromiseFlatCString(name).get()); if (!jstr_name) return NS_ERROR_OUT_OF_MEMORY; JSDProperty *prop = JSD_GetValueProperty (mCx, mValue, jstr_name); *_rval = jsdProperty::FromPtr (mCx, prop); return NS_OK; } NS_IMETHODIMP jsdValue::Refresh() { ASSERT_VALID_EPHEMERAL; JSD_RefreshValue (mCx, mValue); return NS_OK; } NS_IMETHODIMP jsdValue::GetWrappedValue() { ASSERT_VALID_EPHEMERAL; nsresult rv; nsCOMPtr 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; jsval *result; rv = cc->GetRetValPtr(&result); if (NS_FAILED(rv)) return rv; if (result) { JSContext *cx; rv = cc->GetJSContext(&cx); if (NS_FAILED(rv)) return rv; *result = JSD_GetValueWrappedJSVal (mCx, mValue); if (!JS_WrapValue(cx, result)) return NS_ERROR_FAILURE; cc->SetReturnValueWasSet(PR_TRUE); } return NS_OK; } NS_IMETHODIMP jsdValue::GetScript(jsdIScript **_rval) { ASSERT_VALID_EPHEMERAL; JSDScript *script = JSD_GetScriptForValue(mCx, mValue); *_rval = jsdScript::FromPtr(mCx, script); return NS_OK; } /****************************************************************************** * debugger service implementation ******************************************************************************/ NS_IMPL_THREADSAFE_ISUPPORTS1(jsdService, jsdIDebuggerService) NS_IMETHODIMP jsdService::GetJSDContext(JSDContext **_rval) { *_rval = mCx; return NS_OK; } NS_IMETHODIMP jsdService::GetFlags (PRUint32 *_rval) { ASSERT_VALID_CONTEXT; *_rval = JSD_GetContextFlags (mCx); return NS_OK; } NS_IMETHODIMP jsdService::SetFlags (PRUint32 flags) { ASSERT_VALID_CONTEXT; JSD_SetContextFlags (mCx, flags); return NS_OK; } NS_IMETHODIMP jsdService::GetImplementationString(nsACString &aImplementationString) { aImplementationString.AssignLiteral(implementationString); return NS_OK; } NS_IMETHODIMP jsdService::GetImplementationMajor(PRUint32 *_rval) { *_rval = JSDS_MAJOR_VERSION; return NS_OK; } NS_IMETHODIMP jsdService::GetImplementationMinor(PRUint32 *_rval) { *_rval = JSDS_MINOR_VERSION; return NS_OK; } NS_IMETHODIMP 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 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 xpc->SetDebugModeWhenPossible(PR_TRUE); } NS_IMETHODIMP jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, JSBool mode) { NS_ASSERTION(NS_IsMainThread(), "wrong thread"); return JS_SetDebugModeForCompartment(cx, comp, mode) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP jsdService::DeactivateDebugger () { if (!mCx) return NS_OK; jsdContext::InvalidateAll(); jsdScript::InvalidateAll(); jsdValue::InvalidateAll(); jsdProperty::InvalidateAll(); jsdStackFrame::InvalidateAll(); ClearAllBreakpoints(); JSD_SetErrorReporter (mCx, NULL, NULL); JSD_SetScriptHook (mCx, NULL, NULL); JSD_ClearThrowHook (mCx); JSD_ClearInterruptHook (mCx); JSD_ClearDebuggerHook (mCx); JSD_ClearDebugBreakHook (mCx); JSD_ClearTopLevelHook (mCx); JSD_ClearFunctionHook (mCx); JSD_DebuggerOff (mCx); mCx = nsnull; mRuntime = nsnull; mOn = PR_FALSE; return NS_OK; } NS_IMETHODIMP jsdService::ActivateDebugger (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; JSContext *cx = JSD_GetDefaultJSContext (mCx); JSObject *glob = JS_GetGlobalObject (cx); /* init xpconnect on the debugger's context in case xpconnect tries to * use it for stuff. */ nsresult rv; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); if (NS_FAILED(rv)) return rv; xpc->InitClasses (cx, glob); /* Start watching for script creation/destruction and manage jsdScript * objects accordingly */ JSD_SetScriptHook (mCx, jsds_ScriptHookProc, NULL); /* If any of these mFooHook objects are installed, do the required JSD * hookup now. See also, jsdService::SetFooHook(). */ if (mErrorHook) JSD_SetErrorReporter (mCx, jsds_ErrorHookProc, NULL); if (mThrowHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); /* can't ignore script callbacks, as we need to |Release| the wrapper * stored in private data when a script is deleted. */ if (mInterruptHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebuggerHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebugHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); if (mTopLevelHook) JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearTopLevelHook (mCx); if (mFunctionHook) 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; if (!mCx || !mRuntime) return NS_ERROR_NOT_INITIALIZED; if (gDeadScripts) { if (gGCStatus != JSGC_END) return NS_ERROR_NOT_AVAILABLE; JSContext *cx = JSD_GetDefaultJSContext(mCx); while (gDeadScripts) jsds_NotifyPendingDeadScripts (cx); } /* if (gLastGCProc != jsds_GCCallbackProc) JS_SetGCCallbackRT (mRuntime, gLastGCProc); */ DeactivateDebugger(); #ifdef DEBUG 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; } NS_IMETHODIMP jsdService::GetPauseDepth(PRUint32 *_rval) { NS_ENSURE_ARG_POINTER(_rval); *_rval = mPauseLevel; return NS_OK; } NS_IMETHODIMP jsdService::Pause(PRUint32 *_rval) { 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); } if (_rval) *_rval = mPauseLevel; return NS_OK; } NS_IMETHODIMP jsdService::UnPause(PRUint32 *_rval) { 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. */ if (--mPauseLevel == 0 && mOn) { JSD_DebuggerUnpause (mCx); if (mErrorHook) JSD_SetErrorReporter (mCx, jsds_ErrorHookProc, NULL); if (mThrowHook) JSD_SetThrowHook (mCx, jsds_ExecutionHookProc, NULL); if (mInterruptHook) JSD_SetInterruptHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebuggerHook) JSD_SetDebuggerHook (mCx, jsds_ExecutionHookProc, NULL); if (mDebugHook) JSD_SetDebugBreakHook (mCx, jsds_ExecutionHookProc, NULL); if (mTopLevelHook) JSD_SetTopLevelHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearTopLevelHook (mCx); if (mFunctionHook) JSD_SetFunctionHook (mCx, jsds_CallHookProc, NULL); else JSD_ClearFunctionHook (mCx); } if (_rval) *_rval = mPauseLevel; return NS_OK; } NS_IMETHODIMP jsdService::EnumerateContexts (jsdIContextEnumerator *enumerator) { ASSERT_VALID_CONTEXT; if (!enumerator) return NS_OK; JSContext *iter = NULL; JSContext *cx; while ((cx = JS_ContextIterator (mRuntime, &iter))) { nsCOMPtr jsdicx = getter_AddRefs(jsdContext::FromPtr(mCx, cx)); if (jsdicx) { if (NS_FAILED(enumerator->EnumerateContext(jsdicx))) break; } } return NS_OK; } NS_IMETHODIMP jsdService::EnumerateScripts (jsdIScriptEnumerator *enumerator) { ASSERT_VALID_CONTEXT; JSDScript *script; JSDScript *iter = NULL; nsresult rv = NS_OK; JSD_LockScriptSubsystem(mCx); while((script = JSD_IterateScripts(mCx, &iter))) { nsCOMPtr jsdis = getter_AddRefs(jsdScript::FromPtr(mCx, script)); rv = enumerator->EnumerateScript (jsdis); if (NS_FAILED(rv)) break; } JSD_UnlockScriptSubsystem(mCx); return rv; } NS_IMETHODIMP jsdService::GC (void) { ASSERT_VALID_CONTEXT; JSContext *cx = JSD_GetDefaultJSContext (mCx); JS_GC(cx); return NS_OK; } NS_IMETHODIMP jsdService::DumpHeap(const nsACString &fileName) { ASSERT_VALID_CONTEXT; #ifndef DEBUG return NS_ERROR_NOT_IMPLEMENTED; #else nsresult rv = NS_OK; FILE *file = !fileName.IsEmpty() ? fopen(PromiseFlatCString(fileName).get(), "w") : stdout; if (!file) { rv = NS_ERROR_FAILURE; } else { JSContext *cx = JSD_GetDefaultJSContext (mCx); if (!JS_DumpHeap(cx, file, NULL, 0, NULL, (size_t)-1, NULL)) rv = NS_ERROR_FAILURE; if (file != stdout) fclose(file); } return rv; #endif } NS_IMETHODIMP jsdService::ClearProfileData () { ASSERT_VALID_CONTEXT; JSD_ClearAllProfileData (mCx); return NS_OK; } NS_IMETHODIMP jsdService::InsertFilter (jsdIFilter *filter, jsdIFilter *after) { NS_ENSURE_ARG_POINTER (filter); if (jsds_FindFilter (filter)) return NS_ERROR_INVALID_ARG; FilterRecord *rec = PR_NEWZAP (FilterRecord); if (!rec) return NS_ERROR_OUT_OF_MEMORY; if (!jsds_SyncFilter (rec, filter)) { PR_Free (rec); return NS_ERROR_FAILURE; } if (gFilters) { if (!after) { /* insert at head of list */ PR_INSERT_LINK(&rec->links, &gFilters->links); gFilters = rec; } else { /* insert somewhere in the list */ FilterRecord *afterRecord = jsds_FindFilter (after); if (!afterRecord) { jsds_FreeFilter(rec); return NS_ERROR_INVALID_ARG; } PR_INSERT_AFTER(&rec->links, &afterRecord->links); } } else { if (after) { /* user asked to insert into the middle of an empty list, bail. */ jsds_FreeFilter(rec); return NS_ERROR_NOT_INITIALIZED; } PR_INIT_CLIST(&rec->links); gFilters = rec; } return NS_OK; } NS_IMETHODIMP jsdService::AppendFilter (jsdIFilter *filter) { NS_ENSURE_ARG_POINTER (filter); if (jsds_FindFilter (filter)) return NS_ERROR_INVALID_ARG; FilterRecord *rec = PR_NEWZAP (FilterRecord); if (!jsds_SyncFilter (rec, filter)) { PR_Free (rec); return NS_ERROR_FAILURE; } if (gFilters) { PR_INSERT_BEFORE(&rec->links, &gFilters->links); } else { PR_INIT_CLIST(&rec->links); gFilters = rec; } return NS_OK; } NS_IMETHODIMP jsdService::RemoveFilter (jsdIFilter *filter) { NS_ENSURE_ARG_POINTER(filter); FilterRecord *rec = jsds_FindFilter (filter); if (!rec) return NS_ERROR_INVALID_ARG; if (gFilters == rec) { gFilters = reinterpret_cast (PR_NEXT_LINK(&rec->links)); /* If we're the only filter left, null out the list head. */ if (gFilters == rec) gFilters = nsnull; } PR_REMOVE_LINK(&rec->links); jsds_FreeFilter (rec); return NS_OK; } NS_IMETHODIMP jsdService::SwapFilters (jsdIFilter *filter_a, jsdIFilter *filter_b) { NS_ENSURE_ARG_POINTER(filter_a); NS_ENSURE_ARG_POINTER(filter_b); FilterRecord *rec_a = jsds_FindFilter (filter_a); if (!rec_a) return NS_ERROR_INVALID_ARG; if (filter_a == filter_b) { /* just a refresh */ if (!jsds_SyncFilter (rec_a, filter_a)) return NS_ERROR_FAILURE; return NS_OK; } FilterRecord *rec_b = jsds_FindFilter (filter_b); if (!rec_b) { /* filter_b is not in the list, replace filter_a with filter_b. */ if (!jsds_SyncFilter (rec_a, filter_b)) return NS_ERROR_FAILURE; } else { /* both filters are in the list, swap. */ if (!jsds_SyncFilter (rec_a, filter_b)) return NS_ERROR_FAILURE; if (!jsds_SyncFilter (rec_b, filter_a)) return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP jsdService::EnumerateFilters (jsdIFilterEnumerator *enumerator) { if (!gFilters) return NS_OK; FilterRecord *current = gFilters; do { jsds_SyncFilter (current, current->filterObject); /* SyncFilter failure would be bad, but what would we do about it? */ if (enumerator) { nsresult rv = enumerator->EnumerateFilter (current->filterObject); if (NS_FAILED(rv)) return rv; } current = reinterpret_cast (PR_NEXT_LINK (¤t->links)); } while (current != gFilters); return NS_OK; } NS_IMETHODIMP jsdService::RefreshFilters () { return EnumerateFilters(nsnull); } NS_IMETHODIMP jsdService::ClearFilters () { if (!gFilters) return NS_OK; FilterRecord *current = reinterpret_cast (PR_NEXT_LINK (&gFilters->links)); do { FilterRecord *next = reinterpret_cast (PR_NEXT_LINK (¤t->links)); PR_REMOVE_AND_INIT_LINK(¤t->links); jsds_FreeFilter(current); current = next; } while (current != gFilters); jsds_FreeFilter(current); gFilters = nsnull; return NS_OK; } NS_IMETHODIMP jsdService::ClearAllBreakpoints (void) { ASSERT_VALID_CONTEXT; JSD_LockScriptSubsystem(mCx); JSD_ClearAllExecutionHooks (mCx); JSD_UnlockScriptSubsystem(mCx); return NS_OK; } NS_IMETHODIMP jsdService::WrapValue(jsdIValue **_rval) { ASSERT_VALID_CONTEXT; nsresult rv; nsCOMPtr 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; PRUint32 argc; rv = cc->GetArgc (&argc); if (NS_FAILED(rv)) return rv; if (argc < 1) return NS_ERROR_INVALID_ARG; jsval *argv; rv = cc->GetArgvPtr (&argv); if (NS_FAILED(rv)) return rv; return WrapJSValue(argv[0], _rval); } NS_IMETHODIMP jsdService::WrapJSValue(const jsval &value, jsdIValue** _rval) { JSDValue *jsdv = JSD_NewValue(mCx, value); if (!jsdv) return NS_ERROR_FAILURE; *_rval = jsdValue::FromPtr (mCx, jsdv); return NS_OK; } NS_IMETHODIMP jsdService::EnterNestedEventLoop (jsdINestCallback *callback, PRUint32 *_rval) { // Nesting event queues is a thing of the past. Now, we just spin the // current event loop. nsresult rv; nsCOMPtr stack(do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv)); if (NS_FAILED(rv)) return rv; PRUint32 nestLevel = ++mNestedLoopLevel; nsCOMPtr thread = do_GetCurrentThread(); if (NS_SUCCEEDED(stack->Push(nsnull))) { if (callback) { Pause(nsnull); rv = callback->OnNest(); UnPause(nsnull); } while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) { if (!NS_ProcessNextEvent(thread)) rv = NS_ERROR_UNEXPECTED; } JSContext* cx; stack->Pop(&cx); NS_ASSERTION(cx == nsnull, "JSContextStack mismatch"); } else rv = NS_ERROR_FAILURE; 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::SetErrorHook (jsdIErrorHook *aHook) { mErrorHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * 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); return NS_OK; } NS_IMETHODIMP jsdService::GetErrorHook (jsdIErrorHook **aHook) { *aHook = mErrorHook; NS_IF_ADDREF(*aHook); return NS_OK; } 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::SetDebugHook (jsdIExecutionHook *aHook) { mDebugHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * 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); return NS_OK; } NS_IMETHODIMP jsdService::GetDebugHook (jsdIExecutionHook **aHook) { *aHook = mDebugHook; 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 * 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); 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 * 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); 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 * 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 * private data. */ 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 * 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); return NS_OK; } NS_IMETHODIMP jsdService::GetThrowHook (jsdIExecutionHook **aHook) { *aHook = mThrowHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetTopLevelHook (jsdICallHook *aHook) { mTopLevelHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * 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); return NS_OK; } NS_IMETHODIMP jsdService::GetTopLevelHook (jsdICallHook **aHook) { *aHook = mTopLevelHook; NS_IF_ADDREF(*aHook); return NS_OK; } NS_IMETHODIMP jsdService::SetFunctionHook (jsdICallHook *aHook) { mFunctionHook = aHook; /* if the debugger isn't initialized, that's all we can do for now. The * 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); return NS_OK; } NS_IMETHODIMP jsdService::GetFunctionHook (jsdICallHook **aHook) { *aHook = mFunctionHook; NS_IF_ADDREF(*aHook); return NS_OK; } /* virtual */ jsdService::~jsdService() { ClearFilters(); mErrorHook = nsnull; mBreakpointHook = nsnull; mDebugHook = nsnull; mDebuggerHook = nsnull; mInterruptHook = nsnull; mScriptHook = nsnull; mThrowHook = nsnull; mTopLevelHook = nsnull; mFunctionHook = nsnull; gGCStatus = JSGC_END; Off(); gJsds = nsnull; } 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. this is inserted * and/or removed from the app-start category by the jsdService::initAtStartup * property. */ class jsdASObserver : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER jsdASObserver () {} }; NS_IMPL_THREADSAFE_ISUPPORTS1(jsdASObserver, nsIObserver) NS_IMETHODIMP jsdASObserver::Observe (nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) { nsresult rv; // Hmm. Why is the app-startup observer called multiple times? //NS_ASSERTION(!gJsds, "app startup observer called twice"); nsCOMPtr jsds = do_GetService(jsdServiceCtrID, &rv); if (NS_FAILED(rv)) return rv; PRBool on; rv = jsds->GetIsOn(&on); if (NS_FAILED(rv) || on) return rv; nsCOMPtr rts = do_GetService(NS_JSRT_CTRID, &rv); if (NS_FAILED(rv)) return rv; JSRuntime *rt; rts->GetRuntime (&rt); if (NS_FAILED(rv)) return rv; rv = jsds->ActivateDebugger(rt); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_GENERIC_FACTORY_CONSTRUCTOR(jsdASObserver) NS_DEFINE_NAMED_CID(JSDSERVICE_CID); NS_DEFINE_NAMED_CID(JSDASO_CID); static const mozilla::Module::CIDEntry kJSDCIDs[] = { { &kJSDSERVICE_CID, false, NULL, jsdServiceConstructor }, { &kJSDASO_CID, false, NULL, jsdASObserverConstructor }, { NULL } }; static const mozilla::Module::ContractIDEntry kJSDContracts[] = { { jsdServiceCtrID, &kJSDSERVICE_CID }, { jsdARObserverCtrID, &kJSDASO_CID }, { NULL } }; static const mozilla::Module kJSDModule = { mozilla::Module::kVersion, kJSDCIDs, kJSDContracts }; NSMODULE_DEFN(JavaScript_Debugger) = &kJSDModule; /******************************************************************************** ******************************************************************************** * 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