| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "jsdbgapi.h" |
| #include "jslock.h" |
| #include "jsd_xpc.h" |
| |
| #include "js/GCAPI.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 "nsTArray.h" |
| #include "nsThreadUtils.h" |
| #include "nsMemory.h" |
| #include "jsdebug.h" |
| #include "nsReadableUtils.h" |
| #include "nsCRT.h" |
| #include "nsCycleCollectionParticipant.h" |
| #include "mozilla/Attributes.h" |
| |
| /* XXX DOM dependency */ |
| #include "nsIScriptContext.h" |
| #include "SandboxPrivate.h" |
| #include "nsJSPrincipals.h" |
| #include "nsContentUtils.h" |
| #include "nsCxPusher.h" |
| |
| using mozilla::AutoSafeJSContext; |
| using mozilla::AutoPushJSContext; |
| |
| /* |
| * 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 void |
| jsds_GCSliceCallbackProc (JSRuntime *rt, JS::GCProgress progress, const JS::GCDescription &desc); |
| |
| /******************************************************************************* |
| * 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 |
| uint32_t gScriptCount = 0; |
| uint32_t gValueCount = 0; |
| uint32_t gPropertyCount = 0; |
| uint32_t gContextCount = 0; |
| uint32_t gFrameCount = 0; |
| #endif |
| |
| static jsdService *gJsds = 0; |
| static JS::GCSliceCallback gPrevGCSliceCallback = jsds_GCSliceCallbackProc; |
| static bool gGCRunning = false; |
| |
| static struct DeadScript { |
| PRCList links; |
| JSDContext *jsdc; |
| jsdIScript *script; |
| } *gDeadScripts = nullptr; |
| |
| enum PatternType { |
| ptIgnore = 0U, |
| ptStartsWith = 1U, |
| ptEndsWith = 2U, |
| ptContains = 3U, |
| ptEquals = 4U |
| }; |
| |
| static struct FilterRecord { |
| PRCList links; |
| jsdIFilter *filterObject; |
| nsCString urlPattern; |
| PatternType patternType; |
| uint32_t startLine; |
| uint32_t endLine; |
| } *gFilters = nullptr; |
| |
| static struct LiveEphemeral *gLiveValues = nullptr; |
| static struct LiveEphemeral *gLiveProperties = nullptr; |
| static struct LiveEphemeral *gLiveContexts = nullptr; |
| static struct LiveEphemeral *gLiveStackFrames = nullptr; |
| |
| /******************************************************************************* |
| * utility functions for ephemeral lists |
| *******************************************************************************/ |
| already_AddRefed<jsdIEphemeral> |
| jsds_FindEphemeral (LiveEphemeral **listHead, void *key) |
| { |
| if (!*listHead) |
| return nullptr; |
| |
| LiveEphemeral *lv_record = |
| reinterpret_cast<LiveEphemeral *> |
| (PR_NEXT_LINK(&(*listHead)->links)); |
| do |
| { |
| if (lv_record->key == key) |
| { |
| nsCOMPtr<jsdIEphemeral> ret = lv_record->value; |
| return ret.forget(); |
| } |
| lv_record = reinterpret_cast<LiveEphemeral *> |
| (PR_NEXT_LINK(&lv_record->links)); |
| } |
| while (lv_record != *listHead); |
| |
| return nullptr; |
| } |
| |
| void |
| jsds_InvalidateAllEphemerals (LiveEphemeral **listHead) |
| { |
| LiveEphemeral *lv_record = |
| reinterpret_cast<LiveEphemeral *> |
| (PR_NEXT_LINK(&(*listHead)->links)); |
| do |
| { |
| LiveEphemeral *next = |
| reinterpret_cast<LiveEphemeral *> |
| (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<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 = nullptr; |
| } |
| 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. |
| */ |
| bool |
| jsds_SyncFilter (FilterRecord *rec, jsdIFilter *filter) |
| { |
| NS_ASSERTION (rec, "jsds_SyncFilter without rec"); |
| NS_ASSERTION (filter, "jsds_SyncFilter without filter"); |
| |
| uint32_t startLine; |
| nsresult rv = filter->GetStartLine(&startLine); |
| if (NS_FAILED(rv)) |
| return false; |
| |
| uint32_t endLine; |
| rv = filter->GetStartLine(&endLine); |
| if (NS_FAILED(rv)) |
| return false; |
| |
| nsAutoCString urlPattern; |
| rv = filter->GetUrlPattern (urlPattern); |
| if (NS_FAILED(rv)) |
| return false; |
| |
| uint32_t 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->startLine = startLine; |
| rec->endLine = endLine; |
| |
| rec->urlPattern = urlPattern; |
| |
| return true; |
| |
| } |
| |
| FilterRecord * |
| jsds_FindFilter (jsdIFilter *filter) |
| { |
| if (!gFilters) |
| return nullptr; |
| |
| FilterRecord *current = gFilters; |
| |
| do { |
| if (current->filterObject == filter) |
| return current; |
| current = reinterpret_cast<FilterRecord *> |
| (PR_NEXT_LINK(¤t->links)); |
| } while (current != gFilters); |
| |
| return nullptr; |
| } |
| |
| /* returns true if the hook should be executed. */ |
| bool |
| jsds_FilterHook (JSDContext *jsdc, JSDThreadState *state) |
| { |
| JSDStackFrameInfo *frame = JSD_GetStackFrame (jsdc, state); |
| |
| if (!frame) { |
| NS_WARNING("No frame in threadstate"); |
| return false; |
| } |
| |
| JSDScript *script = JSD_GetScriptForStackFrame (jsdc, state, frame); |
| if (!script) |
| return true; |
| |
| uintptr_t pc = JSD_GetPCForStackFrame (jsdc, state, frame); |
| |
| nsCString url(JSD_GetScriptFilename (jsdc, script)); |
| if (url.IsEmpty()) { |
| NS_WARNING ("Script with no filename"); |
| return false; |
| } |
| |
| if (!gFilters) |
| return true; |
| |
| uint32_t currentLine = JSD_GetClosestLine (jsdc, script, pc); |
| uint32_t len = 0; |
| FilterRecord *currentFilter = gFilters; |
| do { |
| uint32_t flags = 0; |
| |
| #ifdef DEBUG |
| nsresult rv = |
| #endif |
| currentFilter->filterObject->GetFlags(&flags); |
| NS_ASSERTION(NS_SUCCEEDED(rv), "Error getting flags for filter"); |
| |
| if (flags & jsdIFilter::FLAG_ENABLED) { |
| /* If there is no start line, or the start line is before |
| * or equal to the current */ |
| if ((!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; |
| uint32_t 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<FilterRecord *> |
| (PR_NEXT_LINK(¤tFilter->links)); |
| } while (currentFilter != gFilters); |
| |
| return true; |
| |
| } |
| |
| /******************************************************************************* |
| * c callbacks |
| *******************************************************************************/ |
| |
| static void |
| jsds_NotifyPendingDeadScripts (JSRuntime *rt) |
| { |
| jsdService *jsds = gJsds; |
| |
| nsCOMPtr<jsdIScriptHook> hook; |
| if (jsds) { |
| NS_ADDREF(jsds); |
| jsds->GetScriptHook (getter_AddRefs(hook)); |
| jsds->DoPause(nullptr, true); |
| } |
| |
| DeadScript *deadScripts = gDeadScripts; |
| gDeadScripts = nullptr; |
| while (deadScripts) { |
| DeadScript *ds = deadScripts; |
| /* get next deleted script */ |
| deadScripts = reinterpret_cast<DeadScript *> |
| (PR_NEXT_LINK(&ds->links)); |
| if (deadScripts == ds) |
| deadScripts = nullptr; |
| |
| 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->DoUnPause(nullptr, true); |
| NS_RELEASE(jsds); |
| } |
| } |
| |
| static void |
| jsds_GCSliceCallbackProc (JSRuntime *rt, JS::GCProgress progress, const JS::GCDescription &desc) |
| { |
| if (progress == JS::GC_CYCLE_END || progress == JS::GC_SLICE_END) { |
| NS_ASSERTION(gGCRunning, "GC slice callback was missed"); |
| |
| while (gDeadScripts) |
| jsds_NotifyPendingDeadScripts (rt); |
| |
| gGCRunning = false; |
| } else { |
| NS_ASSERTION(!gGCRunning, "should not re-enter GC"); |
| gGCRunning = true; |
| } |
| |
| if (gPrevGCSliceCallback) |
| (*gPrevGCSliceCallback)(rt, progress, desc); |
| } |
| |
| static unsigned |
| jsds_ErrorHookProc (JSDContext *jsdc, JSContext *cx, const char *message, |
| JSErrorReport *report, void *callerdata) |
| { |
| static bool running = false; |
| |
| nsCOMPtr<jsdIErrorHook> hook; |
| gJsds->GetErrorHook(getter_AddRefs(hook)); |
| if (!hook) |
| return JSD_ERROR_REPORTER_PASS_ALONG; |
| |
| if (running) |
| return JSD_ERROR_REPORTER_PASS_ALONG; |
| |
| running = true; |
| |
| nsCOMPtr<jsdIValue> val; |
| if (JS_IsExceptionPending(cx)) { |
| jsval jv; |
| JS_GetPendingException(cx, &jv); |
| JSDValue *jsdv = JSD_NewValue (jsdc, jv); |
| val = getter_AddRefs(jsdValue::FromPtr(jsdc, jsdv)); |
| } |
| |
| nsAutoCString fileName; |
| uint32_t line; |
| uint32_t pos; |
| uint32_t flags; |
| uint32_t errnum; |
| bool 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->DoPause(nullptr, true); |
| hook->OnError (nsDependentCString(message), fileName, line, pos, flags, errnum, val, &rval); |
| gJsds->DoUnPause(nullptr, true); |
| |
| running = false; |
| if (!rval) |
| return JSD_ERROR_REPORTER_DEBUG; |
| |
| return JSD_ERROR_REPORTER_PASS_ALONG; |
| } |
| |
| static JSBool |
| jsds_CallHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate, |
| unsigned type, void* callerdata) |
| { |
| nsCOMPtr<jsdICallHook> 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<jsdIStackFrame> frame = |
| getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, |
| native_frame)); |
| gJsds->DoPause(nullptr, true); |
| hook->OnCall(frame, type); |
| gJsds->DoUnPause(nullptr, true); |
| jsdStackFrame::InvalidateAll(); |
| |
| return JS_TRUE; |
| } |
| |
| static uint32_t |
| jsds_ExecutionHookProc (JSDContext* jsdc, JSDThreadState* jsdthreadstate, |
| unsigned type, void* callerdata, jsval* rval) |
| { |
| nsCOMPtr<jsdIExecutionHook> hook(0); |
| uint32_t hook_rv = JSD_HOOK_RETURN_CONTINUE; |
| nsCOMPtr<jsdIValue> 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. */ |
| uint32_t 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<jsdIStackFrame> frame = |
| getter_AddRefs(jsdStackFrame::FromPtr(jsdc, jsdthreadstate, |
| native_frame)); |
| gJsds->DoPause(nullptr, true); |
| jsdIValue *inout_rv = js_rv; |
| NS_IF_ADDREF(inout_rv); |
| hook->OnExecute (frame, type, &inout_rv, &hook_rv); |
| js_rv = inout_rv; |
| NS_IF_RELEASE(inout_rv); |
| gJsds->DoUnPause(nullptr, true); |
| jsdStackFrame::InvalidateAll(); |
| |
| if (hook_rv == JSD_HOOK_RETURN_RET_WITH_VAL || |
| hook_rv == JSD_HOOK_RETURN_THROW_WITH_VAL) { |
| *rval = JSVAL_VOID; |
| if (js_rv) { |
| JSDValue *jsdv; |
| if (NS_SUCCEEDED(js_rv->GetJSDValue (&jsdv))) |
| *rval = JSD_GetValueWrappedJSVal(jsdc, jsdv); |
| } |
| } |
| |
| return hook_rv; |
| } |
| |
| static void |
| jsds_ScriptHookProc (JSDContext* jsdc, JSDScript* jsdscript, JSBool creating, |
| void* callerdata) |
| { |
| #ifdef CAUTIOUS_SCRIPTHOOK |
| JSRuntime *rt = JS_GetRuntime(nsContentUtils::GetSafeJSContext()); |
| #endif |
| |
| if (creating) { |
| nsCOMPtr<jsdIScriptHook> hook; |
| gJsds->GetScriptHook(getter_AddRefs(hook)); |
| |
| /* a script is being created */ |
| if (!hook) { |
| /* nobody cares, just exit */ |
| return; |
| } |
| |
| nsCOMPtr<jsdIScript> script = |
| getter_AddRefs(jsdScript::FromPtr(jsdc, jsdscript)); |
| #ifdef CAUTIOUS_SCRIPTHOOK |
| JS_UNKEEP_ATOMS(rt); |
| #endif |
| gJsds->DoPause(nullptr, true); |
| hook->OnScriptCreated (script); |
| gJsds->DoUnPause(nullptr, true); |
| #ifdef CAUTIOUS_SCRIPTHOOK |
| JS_KEEP_ATOMS(rt); |
| #endif |
| } else { |
| /* a script is being destroyed. even if there is no registered hook |
| * we'll still need to invalidate the jsdIScript record, in order |
| * to remove the reference held in the JSDScript private data. */ |
| nsCOMPtr<jsdIScript> jsdis = |
| static_cast<jsdIScript *>(JSD_GetScriptPrivate(jsdscript)); |
| if (!jsdis) |
| return; |
| |
| jsdis->Invalidate(); |
| |
| if (!gGCRunning) { |
| nsCOMPtr<jsdIScriptHook> 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->DoPause(nullptr, true); |
| hook->OnScriptDestroyed (jsdis); |
| gJsds->DoUnPause(nullptr, true); |
| #ifdef CAUTIOUS_SCRIPTHOOK |
| JS_KEEP_ATOMS(rt); |
| #endif |
| } else { |
| /* if a GC *is* running, we've got to wait until it's done before |
| * we can execute any JS, so we queue the notification in a PRCList |
| * until GC tells us it's done. See jsds_GCCallbackProc(). */ |
| DeadScript *ds = PR_NEW(DeadScript); |
| 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(uint32_t *_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(uint32_t *_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 = 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(bool *_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(uint32_t *_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; |
| } |
| |
| /* Scripts */ |
| NS_IMPL_THREADSAFE_ISUPPORTS2(jsdScript, jsdIScript, jsdIEphemeral) |
| |
| static NS_IMETHODIMP |
| AssignToJSString(JSDContext *aCx, nsACString *x, JSString *str) |
| { |
| if (!str) { |
| x->SetLength(0); |
| return NS_OK; |
| } |
| AutoSafeJSContext cx; |
| JSAutoCompartment ac(cx, JSD_GetDefaultGlobal(aCx)); // Just in case. |
| size_t length = JS_GetStringEncodingLength(cx, str); |
| if (length == size_t(-1)) |
| return NS_ERROR_FAILURE; |
| x->SetLength(uint32_t(length)); |
| if (x->Length() != uint32_t(length)) |
| return NS_ERROR_OUT_OF_MEMORY; |
| JS_EncodeStringToBuffer(cx, str, x->BeginWriting(), length); |
| return NS_OK; |
| } |
| |
| jsdScript::jsdScript (JSDContext *aCx, JSDScript *aScript) : mValid(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(mCx, mFunctionName, str); |
| } |
| mBaseLineNumber = JSD_GetScriptBaseLineNumber(mCx, mScript); |
| mLineExtent = JSD_GetScriptLineExtent(mCx, mScript); |
| mFirstPC = JSD_GetClosestPC(mCx, mScript, 0); |
| JSD_UnlockScriptSubsystem(mCx); |
| |
| mValid = true; |
| } |
| } |
| |
| jsdScript::~jsdScript () |
| { |
| DEBUG_DESTROY ("jsdScript", gScriptCount); |
| delete mFileName; |
| 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() |
| { |
| AutoSafeJSContext cx; |
| JSAutoCompartment ac(cx, JSD_GetDefaultGlobal (mCx)); // Just in case. |
| JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL)); |
| if (!obj) |
| return nullptr; |
| JS::RootedFunction fun(cx, JSD_GetJSFunction (mCx, mScript)); |
| JS::RootedScript script(cx); /* In JSD compartment */ |
| uint32_t baseLine; |
| JS::RootedString jsstr(cx); |
| size_t length; |
| const jschar *chars; |
| |
| if (fun) { |
| unsigned nargs; |
| |
| { |
| JSAutoCompartment ac(cx, JS_GetFunctionObject(fun)); |
| nargs = JS_GetFunctionArgumentCount(cx, fun); |
| if (nargs > 12) |
| return nullptr; |
| jsstr = JS_DecompileFunctionBody (cx, fun, 4); |
| if (!jsstr) |
| return nullptr; |
| |
| if (!(chars = JS_GetStringCharsAndLength(cx, jsstr, &length))) |
| return nullptr; |
| } |
| |
| JS::Anchor<JSString *> 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 nullptr; |
| baseLine = 3; |
| } else { |
| script = JSD_GetJSScript(mCx, mScript); |
| JSString *jsstr; |
| |
| { |
| JSAutoCompartment ac(cx, script); |
| |
| jsstr = JS_DecompileScript (cx, script, "ppscript", 4); |
| if (!jsstr) |
| return nullptr; |
| |
| if (!(chars = JS_GetStringCharsAndLength(cx, jsstr, &length))) |
| return nullptr; |
| } |
| |
| JS::Anchor<JSString *> kungFuDeathGrip(jsstr); |
| script = JS_CompileUCScript (cx, obj, chars, length, "x-jsd:ppbuffer?type=script", 1); |
| if (!script) |
| return nullptr; |
| baseLine = 1; |
| } |
| |
| uint32_t 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<PCMapEntry *> |
| (PR_Malloc((scriptExtent + 1) * sizeof (PCMapEntry))); |
| uint32_t lineMapSize = 0; |
| |
| if (lineMap) { |
| for (uint32_t 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<PCMapEntry *> |
| (PR_Realloc(mPPLineMap = lineMap, |
| lineMapSize * sizeof(PCMapEntry))); |
| if (!lineMap) { |
| PR_Free(mPPLineMap); |
| lineMapSize = 0; |
| } |
| } |
| } |
| |
| mPCMapSize = lineMapSize; |
| return mPPLineMap = lineMap; |
| } |
| |
| uint32_t |
| jsdScript::PPPcToLine (uint32_t aPC) |
| { |
| if (!mPPLineMap && !CreatePPLineMap()) |
| return 0; |
| uint32_t i; |
| for (i = 1; i < mPCMapSize; ++i) { |
| if (mPPLineMap[i].pc > aPC) |
| return mPPLineMap[i - 1].line; |
| } |
| |
| return mPPLineMap[mPCMapSize - 1].line; |
| } |
| |
| uint32_t |
| jsdScript::PPLineToPc (uint32_t aLine) |
| { |
| if (!mPPLineMap && !CreatePPLineMap()) |
| return 0; |
| uint32_t 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 (int32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| AutoSafeJSContext cx; |
| JS::RootedScript script(cx, JSD_GetJSScript(mCx, mScript)); |
| JSAutoCompartment ac(cx, script); |
| *_rval = static_cast<int32_t>(JS_GetScriptVersion(cx, script)); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::GetTag(uint32_t *_rval) |
| { |
| if (!mTag) |
| mTag = ++jsdScript::LastTag; |
| |
| *_rval = mTag; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::Invalidate() |
| { |
| ASSERT_VALID_EPHEMERAL; |
| mValid = false; |
| |
| /* release the addref we do in FromPtr */ |
| jsdIScript *script = static_cast<jsdIScript *> |
| (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<jsdIScript> jsdis = |
| static_cast<jsdIScript *>(JSD_GetScriptPrivate(script)); |
| if (jsdis) |
| jsdis->Invalidate(); |
| } |
| JSD_UnlockScriptSubsystem(cx); |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::GetIsValid(bool *_rval) |
| { |
| *_rval = mValid; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::SetFlags(uint32_t flags) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| JSD_SetScriptFlags(mCx, mScript, flags); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::GetFlags(uint32_t *_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(uint32_t* count, PRUnichar*** paramNames) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| AutoSafeJSContext cx; |
| JS::RootedFunction fun(cx, JSD_GetJSFunction (mCx, mScript)); |
| if (!fun) { |
| *count = 0; |
| *paramNames = nullptr; |
| return NS_OK; |
| } |
| |
| JSAutoCompartment ac(cx, JS_GetFunctionObject(fun)); |
| |
| unsigned nargs; |
| if (!JS_FunctionHasLocalNames(cx, fun) || |
| (nargs = JS_GetFunctionArgumentCount(cx, fun)) == 0) { |
| *count = 0; |
| *paramNames = nullptr; |
| return NS_OK; |
| } |
| |
| PRUnichar **ret = |
| static_cast<PRUnichar**>(NS_Alloc(nargs * sizeof(PRUnichar*))); |
| if (!ret) |
| return NS_ERROR_OUT_OF_MEMORY; |
| |
| void *mark; |
| uintptr_t *names = JS_GetFunctionLocalNameArray(cx, fun, &mark); |
| if (!names) { |
| NS_Free(ret); |
| return NS_ERROR_OUT_OF_MEMORY; |
| } |
| |
| nsresult rv = NS_OK; |
| for (unsigned 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; |
| |
| AutoSafeJSContext jsContext; |
| JS::RootedObject obj(jsContext, 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; |
| AutoSafeJSContext cx_; |
| JSContext *cx = cx_; // Appease the type system with Maybe<>s below. |
| JS::RootedFunction fun(cx, JSD_GetJSFunction (mCx, mScript)); |
| |
| JSString *jsstr; |
| mozilla::Maybe<JSAutoCompartment> ac; |
| if (fun) { |
| ac.construct(cx, JS_GetFunctionObject(fun)); |
| jsstr = JS_DecompileFunction (cx, fun, 4); |
| } else { |
| JS::RootedScript script(cx, JSD_GetJSScript (mCx, mScript)); |
| ac.construct(cx, script); |
| 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(uint32_t *_rval) |
| { |
| *_rval = mBaseLineNumber; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::GetLineExtent(uint32_t *_rval) |
| { |
| *_rval = mLineExtent; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::GetCallCount(uint32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *_rval = JSD_GetScriptCallCount (mCx, mScript); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::GetMaxRecurseDepth(uint32_t *_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(uint32_t aPC, uint32_t aPcmap, uint32_t *_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(uint32_t aLine, uint32_t aPcmap, uint32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| if (aPcmap == PCMAP_SOURCETEXT) { |
| uintptr_t 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(bool 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::GetExecutableLines(uint32_t aPcmap, uint32_t aStartLine, uint32_t aMaxLines, |
| uint32_t* aCount, uint32_t** aExecutableLines) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| if (aPcmap == PCMAP_SOURCETEXT) { |
| uintptr_t start = JSD_GetClosestPC(mCx, mScript, 0); |
| unsigned lastLine = JSD_GetScriptBaseLineNumber(mCx, mScript) |
| + JSD_GetScriptLineExtent(mCx, mScript) - 1; |
| uintptr_t end = JSD_GetClosestPC(mCx, mScript, lastLine + 1); |
| |
| *aExecutableLines = static_cast<uint32_t*>(NS_Alloc((end - start + 1) * sizeof(uint32_t))); |
| if (!JSD_GetLinePCs(mCx, mScript, aStartLine, aMaxLines, aCount, aExecutableLines, NULL)) |
| return NS_ERROR_OUT_OF_MEMORY; |
| |
| return NS_OK; |
| } |
| |
| if (aPcmap == PCMAP_PRETTYPRINT) { |
| if (!mPPLineMap) { |
| if (!CreatePPLineMap()) |
| return NS_ERROR_OUT_OF_MEMORY; |
| } |
| |
| nsTArray<uint32_t> lines; |
| uint32_t i; |
| |
| for (i = 0; i < mPCMapSize; ++i) { |
| if (mPPLineMap[i].line >= aStartLine) |
| break; |
| } |
| |
| for (; i < mPCMapSize && lines.Length() < aMaxLines; ++i) { |
| lines.AppendElement(mPPLineMap[i].line); |
| } |
| |
| if (aCount) |
| *aCount = lines.Length(); |
| |
| *aExecutableLines = static_cast<uint32_t*>(NS_Alloc(lines.Length() * sizeof(uint32_t))); |
| if (!*aExecutableLines) |
| return NS_ERROR_OUT_OF_MEMORY; |
| |
| for (i = 0; i < lines.Length(); ++i) |
| (*aExecutableLines)[i] = lines[i]; |
| |
| return NS_OK; |
| } |
| |
| return NS_ERROR_INVALID_ARG; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::IsLineExecutable(uint32_t aLine, uint32_t aPcmap, bool *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| if (aPcmap == PCMAP_SOURCETEXT) { |
| uintptr_t 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 = false; |
| for (uint32_t 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(uint32_t aPC) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| uintptr_t pc = mFirstPC + aPC; |
| JSD_SetExecutionHook (mCx, mScript, pc, jsds_ExecutionHookProc, NULL); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdScript::ClearBreakpoint(uint32_t aPC) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| uintptr_t 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 nullptr; |
| |
| nsCOMPtr<jsdIContext> jsdicx; |
| nsCOMPtr<jsdIEphemeral> eph = |
| jsds_FindEphemeral (&gLiveContexts, static_cast<void *>(aJSCx)); |
| if (eph) |
| { |
| jsdicx = do_QueryInterface(eph); |
| } |
| else |
| { |
| nsCOMPtr<nsISupports> iscx; |
| if (JS_GetOptions(aJSCx) & JSOPTION_PRIVATE_IS_NSISUPPORTS) |
| iscx = static_cast<nsISupports *>(JS_GetContextPrivate(aJSCx)); |
| jsdicx = new jsdContext (aJSDCx, aJSCx, iscx); |
| } |
| |
| jsdIContext *ctx = nullptr; |
| jsdicx.swap(ctx); |
| return ctx; |
| } |
| |
| jsdContext::jsdContext (JSDContext *aJSDCx, JSContext *aJSCx, |
| nsISupports *aISCx) : mValid(true), mTag(0), |
| mJSDCx(aJSDCx), |
| mJSCx(aJSCx), mISCx(aISCx) |
| { |
| DEBUG_CREATE ("jsdContext", gContextCount); |
| mLiveListEntry.value = this; |
| mLiveListEntry.key = static_cast<void *>(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(bool *_rval) |
| { |
| *_rval = mValid; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdContext::Invalidate() |
| { |
| ASSERT_VALID_EPHEMERAL; |
| mValid = 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(uint32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *_rval = JS_GetOptions(mJSCx); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdContext::SetOptions(uint32_t options) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| uint32_t 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; |
| uint32_t options = JS_GetOptions(mJSCx); |
| if (options & JSOPTION_PRIVATE_IS_NSISUPPORTS) |
| { |
| *_rval = static_cast<nsISupports*>(JS_GetContextPrivate(mJSCx)); |
| NS_IF_ADDREF(*_rval); |
| } |
| else |
| { |
| *_rval = nullptr; |
| } |
| |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdContext::GetWrappedContext(nsISupports **_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| NS_IF_ADDREF(*_rval = mISCx); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdContext::GetTag(uint32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| if (!mTag) |
| mTag = ++jsdContext::LastTag; |
| |
| *_rval = mTag; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdContext::GetGlobalObject (jsdIValue **_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| JSObject *glob = js::GetDefaultGlobalForContext(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 (bool *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| if (!mISCx) { |
| *_rval = true; |
| return NS_OK; |
| } |
| |
| nsCOMPtr<nsIScriptContext> context = do_QueryInterface(mISCx); |
| if (!context) |
| return NS_ERROR_NO_INTERFACE; |
| |
| *_rval = context->GetScriptsEnabled(); |
| |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdContext::SetScriptsEnabled (bool _rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| if (!mISCx) { |
| if (_rval) |
| return NS_OK; |
| return NS_ERROR_NO_INTERFACE; |
| } |
| |
| nsCOMPtr<nsIScriptContext> context = do_QueryInterface(mISCx); |
| if (!context) |
| return NS_ERROR_NO_INTERFACE; |
| |
| context->SetScriptsEnabled(_rval, 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 nullptr; |
| |
| jsdIStackFrame *rv; |
| nsCOMPtr<jsdIStackFrame> frame; |
| |
| nsCOMPtr<jsdIEphemeral> eph = |
| jsds_FindEphemeral (&gLiveStackFrames, |
| reinterpret_cast<void *>(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 = 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(bool *_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(mCx, &_rval, str); |
| |
| _rval.Assign("anonymous"); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdStackFrame::GetIsDebugger(bool *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *_rval = JSD_IsStackFrameDebugger (mCx, mThreadState, mStackFrameInfo); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdStackFrame::GetIsConstructing(bool *_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(uint32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, |
| mStackFrameInfo); |
| if (!script) |
| return NS_ERROR_FAILURE; |
| uintptr_t pcbase = JSD_GetClosestPC(mCx, script, 0); |
| |
| uintptr_t pc = JSD_GetPCForStackFrame (mCx, mThreadState, mStackFrameInfo); |
| if (pc) |
| *_rval = pc - pcbase; |
| else |
| *_rval = pcbase; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdStackFrame::GetLine(uint32_t *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| JSDScript *script = JSD_GetScriptForStackFrame (mCx, mThreadState, |
| mStackFrameInfo); |
| if (script) { |
| uintptr_t 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, |
| uint32_t line, jsdIValue **result, bool *_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<const jschar *>(h.get()); |
| |
| JSExceptionState *estate = 0; |
| |
| AutoPushJSContext cx(JSD_GetJSContext (mCx, mThreadState)); |
| |
| JS::RootedValue jv(cx); |
| |
| estate = JS_SaveExceptionState (cx); |
| JS_ClearPendingException (cx); |
| |
| *_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.address()); |
| else |
| jv = JSVAL_NULL; |
| } |
| |
| JS_RestoreExceptionState (cx, estate); |
| |
| 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 nullptr; |
| |
| jsdIValue *rv = new jsdValue (aCx, aValue); |
| NS_IF_ADDREF(rv); |
| return rv; |
| } |
| |
| jsdValue::jsdValue (JSDContext *aCx, JSDValue *aValue) : mValid(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(bool *_rval) |
| { |
| *_rval = mValid; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdValue::Invalidate() |
| { |
| ASSERT_VALID_EPHEMERAL; |
| mValid = 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 (bool *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *_rval = JSD_IsValueNative (mCx, mValue); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdValue::GetIsNumber (bool *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *_rval = JSD_IsValueNumber (mCx, mValue); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdValue::GetIsPrimitive (bool *_rval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *_rval = JSD_IsValuePrimitive (mCx, mValue); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdValue::GetJsType (uint32_t *_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_PRIMITIVE(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(mCx, &_rval, JSD_GetValueFunctionId(mCx, mValue)); |
| } |
| |
| NS_IMETHODIMP |
| jsdValue::GetBooleanValue(bool *_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(int32_t *_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; |
| AutoSafeJSContext cx; |
| 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 (int32_t *_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, uint32_t *length) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| *propArray = nullptr; |
| if (length) |
| *length = 0; |
| |
| uint32_t prop_count = JSD_IsValueObject(mCx, mValue) |
| ? JSD_GetCountOfProperties (mCx, mValue) |
| : 0; |
| NS_ENSURE_TRUE(prop_count, NS_OK); |
| |
| jsdIProperty **pa_temp = |
| static_cast<jsdIProperty **> |
| (nsMemory::Alloc(sizeof (jsdIProperty *) * |
| prop_count)); |
| NS_ENSURE_TRUE(pa_temp, NS_ERROR_OUT_OF_MEMORY); |
| |
| uint32_t 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; |
| AutoSafeJSContext cx; |
| JSAutoCompartment ac(cx, JSD_GetDefaultGlobal (mCx)); // Just in case. |
| |
| /* 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(JSContext* aCx, JS::Value* aRetval) |
| { |
| ASSERT_VALID_EPHEMERAL; |
| |
| *aRetval = JSD_GetValueWrappedJSVal(mCx, mValue); |
| if (!JS_WrapValue(aCx, aRetval)) { |
| return NS_ERROR_FAILURE; |
| } |
| |
| 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_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(jsdService) |
| NS_INTERFACE_MAP_ENTRY(jsdIDebuggerService) |
| NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, jsdIDebuggerService) |
| NS_INTERFACE_MAP_END |
| |
| NS_IMPL_CYCLE_COLLECTION_10(jsdService, |
| mErrorHook, mBreakpointHook, mDebugHook, |
| mDebuggerHook, mInterruptHook, mScriptHook, |
| mThrowHook, mTopLevelHook, mFunctionHook, |
| mActivationCallback) |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(jsdService) |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(jsdService) |
| |
| NS_IMETHODIMP |
| jsdService::GetJSDContext(JSDContext **_rval) |
| { |
| *_rval = mCx; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::GetFlags (uint32_t *_rval) |
| { |
| ASSERT_VALID_CONTEXT; |
| *_rval = JSD_GetContextFlags (mCx); |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::SetFlags (uint32_t 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(uint32_t *_rval) |
| { |
| *_rval = JSDS_MAJOR_VERSION; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::GetImplementationMinor(uint32_t *_rval) |
| { |
| *_rval = JSDS_MINOR_VERSION; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::GetIsOn (bool *_rval) |
| { |
| *_rval = mOn; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::On (void) |
| { |
| return NS_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::AsyncOn (jsdIActivationCallback *activationCallback) |
| { |
| nsresult rv; |
| |
| nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv); |
| if (NS_FAILED(rv)) return rv; |
| |
| mActivationCallback = activationCallback; |
| |
| return xpc->SetDebugModeWhenPossible(true, true); |
| } |
| |
| NS_IMETHODIMP |
| jsdService::RecompileForDebugMode (JSContext *cx, JSCompartment *comp, bool mode) { |
| NS_ASSERTION(NS_IsMainThread(), "wrong thread"); |
| /* XPConnect now does this work itself, so this IDL entry point is no longer used. */ |
| return NS_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| 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 = nullptr; |
| mRuntime = nullptr; |
| mOn = false; |
| |
| return NS_OK; |
| } |
| |
| |
| NS_IMETHODIMP |
| jsdService::ActivateDebugger (JSRuntime *rt) |
| { |
| if (mOn) |
| return (rt == mRuntime) ? NS_OK : NS_ERROR_ALREADY_INITIALIZED; |
| |
| mRuntime = rt; |
| |
| if (gPrevGCSliceCallback == jsds_GCSliceCallbackProc) |
| /* condition indicates that the callback proc has not been set yet */ |
| gPrevGCSliceCallback = JS::SetGCSliceCallback (rt, jsds_GCSliceCallbackProc); |
| |
| mCx = JSD_DebuggerOnForUser (rt, NULL, NULL); |
| if (!mCx) |
| return NS_ERROR_FAILURE; |
| |
| AutoSafeJSContext cx; |
| JS::RootedObject glob(cx, JSD_GetDefaultGlobal (mCx)); |
| JSAutoCompartment ac(cx, glob); |
| |
| /* init xpconnect on the debugger's context in case xpconnect tries to |
| * use it for stuff. */ |
| nsresult rv; |
| nsCOMPtr<nsIXPConnect> 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 = true; |
| |
| #ifdef DEBUG |
| printf ("+++ JavaScript debugging hooks installed.\n"); |
| #endif |
| |
| nsCOMPtr<jsdIActivationCallback> activationCallback; |
| mActivationCallback.swap(activationCallback); |
| if (activationCallback) |
| return activationCallback->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 (gGCRunning) |
| return NS_ERROR_NOT_AVAILABLE; |
| |
| while (gDeadScripts) |
| jsds_NotifyPendingDeadScripts (JS_GetRuntime(nsContentUtils::GetSafeJSContext())); |
| } |
| |
| DeactivateDebugger(); |
| |
| #ifdef DEBUG |
| printf ("+++ JavaScript debugging hooks removed.\n"); |
| #endif |
| |
| nsresult rv; |
| nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv); |
| if (NS_FAILED(rv)) |
| return rv; |
| |
| xpc->SetDebugModeWhenPossible(false, true); |
| |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::GetPauseDepth(uint32_t *_rval) |
| { |
| NS_ENSURE_ARG_POINTER(_rval); |
| *_rval = mPauseLevel; |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::Pause(uint32_t *_rval) |
| { |
| return DoPause(_rval, false); |
| } |
| |
| nsresult |
| jsdService::DoPause(uint32_t *_rval, bool internalCall) |
| { |
| if (!mCx) |
| return NS_ERROR_NOT_INITIALIZED; |
| |
| if (++mPauseLevel == 1) { |
| JSD_SetErrorReporter (mCx, NULL, NULL); |
| JSD_ClearThrowHook (mCx); |
| JSD_ClearInterruptHook (mCx); |
| JSD_ClearDebuggerHook (mCx); |
| JSD_ClearDebugBreakHook (mCx); |
| JSD_ClearTopLevelHook (mCx); |
| JSD_ClearFunctionHook (mCx); |
| JSD_DebuggerPause (mCx); |
| |
| nsresult rv; |
| nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv); |
| if (NS_FAILED(rv)) return rv; |
| |
| if (!internalCall) { |
| rv = xpc->SetDebugModeWhenPossible(false, false); |
| NS_ENSURE_SUCCESS(rv, rv); |
| } |
| } |
| |
| if (_rval) |
| *_rval = mPauseLevel; |
| |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::UnPause(uint32_t *_rval) |
| { |
| return DoUnPause(_rval, false); |
| } |
| |
| nsresult |
| jsdService::DoUnPause(uint32_t *_rval, bool internalCall) |
| { |
| if (!mCx) |
| return NS_ERROR_NOT_INITIALIZED; |
| |
| if (mPauseLevel == 0) |
| return NS_ERROR_NOT_AVAILABLE; |
| |
| /* check mOn before we muck with this stuff, it's possible the debugger |
| * was turned off while we were paused. |
| */ |
| 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); |
| |
| nsresult rv; |
| nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv); |
| if (NS_FAILED(rv)) return rv; |
| |
| if (!internalCall) { |
| rv = xpc->SetDebugModeWhenPossible(true, false); |
| NS_ENSURE_SUCCESS(rv, rv); |
| } |
| } |
| |
| 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<jsdIContext> 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<jsdIScript> 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; |
| JSRuntime *rt = JSD_GetJSRuntime (mCx); |
| JS_GC(rt); |
| 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 { |
| if (!JS_DumpHeap(JS_GetRuntime(nsContentUtils::GetSafeJSContext()), file, NULL, JSTRACE_OBJECT, 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<FilterRecord *> |
| (PR_NEXT_LINK(&rec->links)); |
| /* If we're the only filter left, null out the list head. */ |
| if (gFilters == rec) |
| gFilters = nullptr; |
| } |
| |
| |
| 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<FilterRecord *> |
| (PR_NEXT_LINK (¤t->links)); |
| } while (current != gFilters); |
| |
| return NS_OK; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::RefreshFilters () |
| { |
| return EnumerateFilters(nullptr); |
| } |
| |
| NS_IMETHODIMP |
| jsdService::ClearFilters () |
| { |
| if (!gFilters) |
| return NS_OK; |
| |
| FilterRecord *current = reinterpret_cast<FilterRecord *> |
| (PR_NEXT_LINK (&gFilters->links)); |
| do { |
| FilterRecord *next = reinterpret_cast<FilterRecord *> |
| (PR_NEXT_LINK (¤t->links)); |
| PR_REMOVE_AND_INIT_LINK(¤t->links); |
| jsds_FreeFilter(current); |
| current = next; |
| } while (current != gFilters); |
| |
| jsds_FreeFilter(current); |
| gFilters = nullptr; |
| |
| 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(const JS::Value &value, jsdIValue **_rval) |
| { |
| ASSERT_VALID_CONTEXT; |
| 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, uint32_t *_rval) |
| { |
| // Nesting event queues is a thing of the past. Now, we just spin the |
| // current event loop. |
| nsresult rv = NS_OK; |
| nsCxPusher pusher; |
| pusher.PushNull(); |
| uint32_t nestLevel = ++mNestedLoopLevel; |
| nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); |
| |
| if (callback) { |
| DoPause(nullptr, true); |
| rv = callback->OnNest(); |
| DoUnPause(nullptr, true); |
| } |
| |
| while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) { |
| if (!NS_ProcessNextEvent(thread)) |
| rv = NS_ERROR_UNEXPECTED; |
| } |
| |
| NS_ASSERTION (mNestedLoopLevel <= nestLevel, |
| "nested event didn't unwind properly"); |
| if (mNestedLoopLevel == nestLevel) |
| --mNestedLoopLevel; |
| |
| *_rval = mNestedLoopLevel; |
| return rv; |
| } |
| |
| NS_IMETHODIMP |
| jsdService::ExitNestedEventLoop (uint32_t *_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 = nullptr; |
| mBreakpointHook = nullptr; |
| mDebugHook = nullptr; |
| mDebuggerHook = nullptr; |
| mInterruptHook = nullptr; |
| mScriptHook = nullptr; |
| mThrowHook = nullptr; |
| mTopLevelHook = nullptr; |
| mFunctionHook = nullptr; |
| Off(); |
| gJsds = nullptr; |
| } |
| |
| 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 MOZ_FINAL : 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<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv); |
| if (NS_FAILED(rv)) |
| return rv; |
| |
| bool on; |
| rv = jsds->GetIsOn(&on); |
| if (NS_FAILED(rv) || on) |
| return rv; |
| |
| nsCOMPtr<nsIJSRuntimeService> 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; |
| |
| void |
| global_finalize(JSFreeOp *aFop, JSObject *aObj) |
| { |
| nsIScriptObjectPrincipal *sop = |
| static_cast<nsIScriptObjectPrincipal *>(js::GetObjectPrivate(aObj)); |
| MOZ_ASSERT(sop); |
| static_cast<SandboxPrivate *>(sop)->ForgetGlobalObject(); |
| NS_IF_RELEASE(sop); |
| } |
| |
| JSObject * |
| CreateJSDGlobal(JSContext *aCx, JSClass *aClasp) |
| { |
| nsresult rv; |
| nsCOMPtr<nsIPrincipal> nullPrin = do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); |
| NS_ENSURE_SUCCESS(rv, nullptr); |
| |
| JSPrincipals *jsPrin = nsJSPrincipals::get(nullPrin); |
| JSObject *global = JS_NewGlobalObject(aCx, aClasp, jsPrin); |
| NS_ENSURE_TRUE(global, nullptr); |
| |
| // We have created a new global let's attach a private to it |
| // that implements nsIGlobalObject. |
| nsCOMPtr<nsIScriptObjectPrincipal> sbp = |
| new SandboxPrivate(nullPrin, global); |
| JS_SetPrivate(global, sbp.forget().get()); |
| |
| return global; |
| } |
| |
| /******************************************************************************** |
| ******************************************************************************** |
| * 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 (uint32_t *_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 |