/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape 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/NPL/ * * 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): * * * 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 NPL, 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 NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsJSEnvironment.h" #include "nsIScriptContextOwner.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptGlobalObjectOwner.h" #include "nsIScriptObjectPrincipal.h" #include "nsIDOMWindowInternal.h" #include "nsIDOMNode.h" #include "nsIDOMElement.h" #include "nsIDOMDocument.h" #include "nsIDOMText.h" #include "nsIDOMAttr.h" #include "nsIDOMNamedNodeMap.h" #include "nsIDOMNodeList.h" #include "nsIDOMKeyEvent.h" #include "nsIDOMHTMLImageElement.h" #include "nsIDOMHTMLOptionElement.h" #include "nsIScriptSecurityManager.h" #include "nsDOMCID.h" #include "nsIServiceManager.h" #include "nsIXPConnect.h" #include "nsIJSContextStack.h" #include "nsIJSRuntimeService.h" #include "nsIPref.h" #include "nsCOMPtr.h" #include "nsReadableUtils.h" #include "nsJSUtils.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIPresContext.h" #include "nsIConsoleService.h" #include "nsIScriptError.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPrompt.h" #include "nsIObserverService.h" #include "nsGUIEvent.h" #include "nsScriptNameSpaceManager.h" #include "nsIThread.h" #include "nsITimer.h" #include "nsDOMClassInfo.h" #include "nsIAtom.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" // For locale aware string methods #include "nsUnicharUtils.h" #include "nsILocaleService.h" #include "nsICollation.h" #include "nsCollationCID.h" static NS_DEFINE_CID(kCollationFactoryCID, NS_COLLATIONFACTORY_CID); #ifdef MOZ_LOGGING // Force PR_LOGGING so we can get JS strict warnings even in release builds #define FORCE_PR_LOG 1 #endif #include "prlog.h" #include "prthread.h" #ifdef OJI #include "nsIJVMManager.h" #include "nsILiveConnectManager.h" #endif const size_t gStackSize = 8192; #ifdef PR_LOGGING static PRLogModuleInfo* gJSDiagnostics; #endif // Thank you Microsoft! #ifdef CompareString #undef CompareString #endif #define NS_GC_DELAY 2000 // ms #define NS_FIRST_GC_DELAY 10000 // ms static nsITimer *sGCTimer; static PRBool sReadyForGC; nsScriptNameSpaceManager *gNameSpaceManager; static nsIJSRuntimeService *sRuntimeService; JSRuntime *nsJSEnvironment::sRuntime; static const char kJSRuntimeServiceContractID[] = "@mozilla.org/js/xpc/RuntimeService;1"; static PRThread *gDOMThread; static JSGCCallback gOldJSGCCallback; static PRBool sDidShutdown; static PRInt32 sContextCount; static PRTime sMaxScriptRunTime; static nsIScriptSecurityManager *sSecurityManager; static nsICollation *gCollation; void JS_DLL_CALLBACK NS_ScriptErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) { // XXX this means we are not going to get error reports on non DOM contexts nsIScriptContext *context = nsJSUtils::GetDynamicScriptContext(cx); nsEventStatus status = nsEventStatus_eIgnore; if (context) { nsIScriptGlobalObject *globalObject = context->GetGlobalObject(); if (globalObject) { nsAutoString fileName, msg; if (report) { fileName.AssignWithConversion(report->filename); const PRUnichar *m = NS_REINTERPRET_CAST(const PRUnichar*, report->ucmessage); if (m) { msg.Assign(m); } } if (msg.IsEmpty() && message) { msg.AssignWithConversion(message); } // First, notify the DOM that we have a script error. nsIDocShell *docShell = globalObject->GetDocShell(); if (docShell && !JSREPORT_IS_WARNING(report->flags)) { static PRInt32 errorDepth; // Recursion prevention ++errorDepth; nsCOMPtr presContext; docShell->GetPresContext(getter_AddRefs(presContext)); if (presContext && errorDepth < 2) { nsScriptErrorEvent errorevent(NS_SCRIPT_ERROR); errorevent.fileName = fileName.get(); errorevent.errorMsg = msg.get(); errorevent.lineNr = report ? report->lineno : 0; // HandleDOMEvent() must be synchronous for the recursion block // (errorDepth) to work. globalObject->HandleDOMEvent(presContext, &errorevent, nsnull, NS_EVENT_FLAG_INIT, &status); } --errorDepth; } if (status != nsEventStatus_eConsumeNoDefault) { // Make an nsIScriptError and populate it with information from // this error. nsCOMPtr errorObject = do_CreateInstance("@mozilla.org/scripterror;1"); if (errorObject != nsnull) { nsresult rv; const char *category = nsnull; // Set category to XUL or content, if possible. if (docShell) { nsCOMPtr docShellTI(do_QueryInterface(docShell, &rv)); if (NS_SUCCEEDED(rv) && docShellTI) { PRInt32 docShellType; rv = docShellTI->GetItemType(&docShellType); if (NS_SUCCEEDED(rv)) { category = docShellType == nsIDocShellTreeItem::typeChrome ? "chrome javascript" : "content javascript"; } } } if (report) { PRUint32 column = report->uctokenptr - report->uclinebuf; rv = errorObject->Init(msg.get(), fileName.get(), NS_REINTERPRET_CAST(const PRUnichar*, report->uclinebuf), report->lineno, column, report->flags, category); } else if (message) { rv = errorObject->Init(msg.get(), nsnull, nsnull, 0, 0, 0, category); } if (NS_SUCCEEDED(rv)) { nsIScriptGlobalObjectOwner *owner = globalObject->GetGlobalObjectOwner(); if (owner) { owner->ReportScriptError(errorObject); } else { // We lack an owner to report this error to, so let's just // report it to the console service so as to not lose it. nsCOMPtr consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { consoleService->LogMessage(errorObject); } } } } } } } #ifdef DEBUG // Print it to stderr as well, for the benefit of those invoking // mozilla with -console. nsAutoString error; error.Assign(NS_LITERAL_STRING("JavaScript ")); if (JSREPORT_IS_STRICT(report->flags)) error.Append(NS_LITERAL_STRING("strict ")); if (JSREPORT_IS_WARNING(report->flags)) error.Append(NS_LITERAL_STRING("warning: \n")); else error.Append(NS_LITERAL_STRING("error: \n")); error.AppendWithConversion(report->filename); error.Append(NS_LITERAL_STRING(" line ")); error.AppendInt(report->lineno, 10); error.Append(NS_LITERAL_STRING(": ")); error.Append(NS_REINTERPRET_CAST(const PRUnichar*, report->ucmessage)); error.Append(NS_LITERAL_STRING("\n")); if (status != nsEventStatus_eIgnore && !JSREPORT_IS_WARNING(report->flags)) error.Append(NS_LITERAL_STRING("Error was suppressed by event handler\n")); char *errorStr = ToNewCString(error); if (errorStr) { fprintf(stderr, "%s\n", errorStr); fflush(stderr); nsMemory::Free(errorStr); } #endif #ifdef PR_LOGGING if (report) { if (!gJSDiagnostics) gJSDiagnostics = PR_NewLogModule("JSDiagnostics"); if (gJSDiagnostics) { PR_LOG(gJSDiagnostics, JSREPORT_IS_WARNING(report->flags) ? PR_LOG_WARNING : PR_LOG_ERROR, ("file %s, line %u: %s\n%s%s", report->filename, report->lineno, message, report->linebuf ? report->linebuf : "", (report->linebuf && report->linebuf[strlen(report->linebuf)-1] != '\n') ? "\n" : "")); } } #endif // XXX do we really want to be doing this? ::JS_ClearPendingException(cx); } static JSBool ChangeCase(JSContext *cx, JSString *src, jsval *rval, void(* changeCaseFnc)(const nsAString&, nsAString&)) { nsAutoString result; changeCaseFnc(nsDependentJSString(src), result); JSString *ucstr = JS_NewUCStringCopyN(cx, (jschar*)result.get(), result.Length()); if (!ucstr) { return JS_FALSE; } *rval = STRING_TO_JSVAL(ucstr); return JS_TRUE; } static JSBool JS_DLL_CALLBACK LocaleToUpperCase(JSContext *cx, JSString *src, jsval *rval) { return ChangeCase(cx, src, rval, ToUpperCase); } static JSBool JS_DLL_CALLBACK LocaleToLowerCase(JSContext *cx, JSString *src, jsval *rval) { return ChangeCase(cx, src, rval, ToLowerCase); } static JSBool JS_DLL_CALLBACK LocaleCompare(JSContext *cx, JSString *src1, JSString *src2, jsval *rval) { nsresult rv; if (!gCollation) { nsCOMPtr localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr locale; rv = localeService->GetApplicationLocale(getter_AddRefs(locale)); if (NS_SUCCEEDED(rv)) { nsCOMPtr colFactory = do_CreateInstance(kCollationFactoryCID, &rv); if (NS_SUCCEEDED(rv)) { rv = colFactory->CreateCollation(locale, &gCollation); } } } if (NS_FAILED(rv)) { nsDOMClassInfo::ThrowJSException(cx, rv); return JS_FALSE; } } PRInt32 result; rv = gCollation->CompareString(nsICollation::kCollationStrengthDefault, nsDependentJSString(src1), nsDependentJSString(src2), &result); if (NS_FAILED(rv)) { nsDOMClassInfo::ThrowJSException(cx, rv); return JS_FALSE; } *rval = INT_TO_JSVAL(result); return JS_TRUE; } static void NotifyXPCIfExceptionPending(JSContext *cx) { if (!::JS_IsExceptionPending(cx)) { return; } nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID())); if (xpc) { nsCOMPtr nccx; xpc->GetCurrentNativeCallContext(getter_AddRefs(nccx)); if (nccx) { nccx->SetExceptionWasThrown(PR_TRUE); } } } // The number of branch callbacks between calls to JS_MaybeGC #define MAYBE_GC_BRANCH_COUNT_MASK 0x00000fff // 4095 // The number of branch callbacks between elapsed time checks. This is // slightly more than the number of callbacks a slow machine makes in a second // when running a script that makes callbacks infrequently. #define MAYBE_STOP_BRANCH_COUNT_MASK 0x00007fff // 32767 // This function is called after each JS branch callback JSBool JS_DLL_CALLBACK nsJSContext::DOMBranchCallback(JSContext *cx, JSScript *script) { // Get the native context nsJSContext *ctx = NS_STATIC_CAST(nsJSContext *, ::JS_GetContextPrivate(cx)); // Filter out most of the calls to this callback if (++ctx->mBranchCallbackCount & MAYBE_GC_BRANCH_COUNT_MASK) return JS_TRUE; // Run the GC if we get this far. JS_MaybeGC(cx); // Filter out even more of the calls to this callback if (ctx->mBranchCallbackCount & MAYBE_STOP_BRANCH_COUNT_MASK) return JS_TRUE; PRTime now = PR_Now(); // If this is the first time we've made it this far, we start timing how // long the script has run if (LL_IS_ZERO(ctx->mBranchCallbackTime)) { ctx->mBranchCallbackTime = now; return JS_TRUE; } PRTime duration; LL_SUB(duration, now, ctx->mBranchCallbackTime); // Check the amount of time this script has been running if (LL_CMP(duration, <, sMaxScriptRunTime)) return JS_TRUE; // If we get here we're most likely executing an infinite loop in JS, // we'll tell the user about this and we'll give the user the option // of stopping the execution of the script. nsIScriptGlobalObject *global = ctx->GetGlobalObject(); NS_ENSURE_TRUE(global, JS_TRUE); nsIDocShell *docShell = global->GetDocShell(); NS_ENSURE_TRUE(docShell, JS_TRUE); nsCOMPtr ireq(do_QueryInterface(docShell)); NS_ENSURE_TRUE(ireq, JS_TRUE); // Get the nsIPrompt interface from the docshell nsCOMPtr prompt; ireq->GetInterface(NS_GET_IID(nsIPrompt), getter_AddRefs(prompt)); NS_ENSURE_TRUE(prompt, JS_TRUE); NS_NAMED_LITERAL_STRING(title, "Script warning"); NS_NAMED_MULTILINE_LITERAL_STRING(msg, NS_L("A script on this page is causing mozilla to ") NS_L("run slowly. If it continues to run, your ") NS_L("computer may become unresponsive.\n\nDo you ") NS_L("want to abort the script?")); JSBool ret = JS_TRUE; // Open the dialog. nsresult rv = prompt->Confirm(title.get(), msg.get(), &ret); if (NS_FAILED(rv) || !ret) { // Allow the script to run this long again ctx->mBranchCallbackTime = PR_Now(); return JS_TRUE; } return JS_FALSE; } #define JS_OPTIONS_DOT_STR "javascript.options." static const char js_options_dot_str[] = JS_OPTIONS_DOT_STR; static const char js_strict_option_str[] = JS_OPTIONS_DOT_STR "strict"; static const char js_werror_option_str[] = JS_OPTIONS_DOT_STR "werror"; int PR_CALLBACK nsJSContext::JSOptionChangedCallback(const char *pref, void *data) { nsresult rv; nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { nsJSContext *context = NS_REINTERPRET_CAST(nsJSContext *, data); PRUint32 oldDefaultJSOptions = context->mDefaultJSOptions; PRUint32 newDefaultJSOptions = oldDefaultJSOptions; PRBool strict; if (NS_SUCCEEDED(prefs->GetBoolPref(js_strict_option_str, &strict))) { if (strict) newDefaultJSOptions |= JSOPTION_STRICT; else newDefaultJSOptions &= ~JSOPTION_STRICT; } PRBool werror; if (NS_SUCCEEDED(prefs->GetBoolPref(js_werror_option_str, &werror))) { if (werror) newDefaultJSOptions |= JSOPTION_WERROR; else newDefaultJSOptions &= ~JSOPTION_WERROR; } if (newDefaultJSOptions != oldDefaultJSOptions) { // Set options only if we used the old defaults; otherwise the page has // customized some via the options object and we defer to its wisdom. if (::JS_GetOptions(context->mContext) == oldDefaultJSOptions) ::JS_SetOptions(context->mContext, newDefaultJSOptions); // Save the new defaults for the next page load (InitContext). context->mDefaultJSOptions = newDefaultJSOptions; } } return 0; } static jsuword GetThreadStackLimit() { // Store the thread stack limit in a static local to ensure that all // contexts get the same stack limit (they're all on the same thread // anyways), and this also helps prevent returning a stack limit // that is beyond the end of the stack if this method is called way // deep on the stack. static jsuword sThreadStackLimit; if (sThreadStackLimit == 0) { int stackDummy; jsuword currentStackAddr = (jsuword)&stackDummy; // We assume here that the stack grows down, and that a stack // limit of 512k below the current stack addr is ok. If this is // not the case on some platforms, #ifdef's are needed for those // platforms. sThreadStackLimit = currentStackAddr + (0x80000 * JS_STACK_GROWTH_DIRECTION); } return sThreadStackLimit; } nsJSContext::nsJSContext(JSRuntime *aRuntime) : mGCOnDestruction(PR_TRUE) { ++sContextCount; mDefaultJSOptions = JSOPTION_PRIVATE_IS_NSISUPPORTS #ifdef DEBUG | JSOPTION_STRICT // lint catching for development #endif ; // Let xpconnect resync its JSContext tracker. We do this before creating // a new JSContext just in case the heap manager recycles the JSContext // struct. nsresult rv; nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); if (NS_SUCCEEDED(rv)) xpc->SyncJSContexts(); mContext = ::JS_NewContext(aRuntime, gStackSize); if (mContext) { ::JS_SetContextPrivate(mContext, NS_STATIC_CAST(nsIScriptContext *, this)); ::JS_SetThreadStackLimit(mContext, GetThreadStackLimit()); // Make sure the new context gets the default context options ::JS_SetOptions(mContext, mDefaultJSOptions); // Check for the JS strict option, which enables extra error checks nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { prefs->RegisterCallback(js_options_dot_str, JSOptionChangedCallback, this); JSOptionChangedCallback(js_options_dot_str, this); } ::JS_SetBranchCallback(mContext, DOMBranchCallback); static JSLocaleCallbacks localeCallbacks = { LocaleToUpperCase, LocaleToLowerCase, LocaleCompare }; ::JS_SetLocaleCallbacks(mContext, &localeCallbacks); } mIsInitialized = PR_FALSE; mNumEvaluations = 0; mOwner = nsnull; mTerminationFunc = nsnull; mScriptsEnabled = PR_TRUE; mBranchCallbackCount = 0; mBranchCallbackTime = LL_ZERO; mProcessingScriptTag=PR_FALSE; InvalidateContextAndWrapperCache(); } nsJSContext::~nsJSContext() { // Cope with JS_NewContext failure in ctor (XXXbe move NewContext to Init?) if (!mContext) return; // Clear our entry in the JSContext, bugzilla bug 66413 ::JS_SetContextPrivate(mContext, nsnull); // Clear the branch callback, bugzilla bug 238218 ::JS_SetBranchCallback(mContext, nsnull); // Unregister our "javascript.options.*" pref-changed callback. nsCOMPtr prefs(do_GetService(NS_PREF_CONTRACTID)); if (prefs) { prefs->UnregisterCallback(js_options_dot_str, JSOptionChangedCallback, this); } // Release mGlobalWrapperRef before the context is destroyed mGlobalWrapperRef = nsnull; // Let xpconnect destroy the JSContext when it thinks the time is right. nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID())); if (xpc) { PRBool do_gc = mGCOnDestruction && !sGCTimer && sReadyForGC; xpc->ReleaseJSContext(mContext, !do_gc); } else { ::JS_DestroyContext(mContext); } --sContextCount; if (!sContextCount && sDidShutdown) { // The last context is being deleted, and we're already in the // process of shutting down, release the JS runtime service, and // the security manager. NS_IF_RELEASE(sRuntimeService); NS_IF_RELEASE(sSecurityManager); NS_IF_RELEASE(gCollation); } } // QueryInterface implementation for nsJSContext NS_INTERFACE_MAP_BEGIN(nsJSContext) NS_INTERFACE_MAP_ENTRY(nsIScriptContext) NS_INTERFACE_MAP_ENTRY(nsIXPCScriptNotify) NS_INTERFACE_MAP_ENTRY(nsITimerCallback) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptContext) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(nsJSContext) NS_IMPL_RELEASE(nsJSContext) nsresult nsJSContext::EvaluateStringWithValue(const nsAString& aScript, void *aScopeObject, nsIPrincipal *aPrincipal, const char *aURL, PRUint32 aLineNo, const char* aVersion, void* aRetValue, PRBool* aIsUndefined) { // Beware that the result is not rooted! Be very careful not to run // the GC before rooting the result somehow! if (!mScriptsEnabled) { if (aIsUndefined) { *aIsUndefined = PR_TRUE; } return NS_OK; } nsresult rv; if (!aScopeObject) aScopeObject = ::JS_GetGlobalObject(mContext); // Safety first: get an object representing the script's principals, i.e., // the entities who signed this script, or the fully-qualified-domain-name // or "codebase" from which it was loaded. JSPrincipals *jsprin; nsCOMPtr principal = aPrincipal; if (aPrincipal) { aPrincipal->GetJSPrincipals(mContext, &jsprin); } else { nsIScriptGlobalObject *global = GetGlobalObject(); if (!global) return NS_ERROR_FAILURE; nsCOMPtr objPrincipal = do_QueryInterface(global, &rv); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; rv = objPrincipal->GetPrincipal(getter_AddRefs(principal)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; principal->GetJSPrincipals(mContext, &jsprin); } // From here on, we must JSPRINCIPALS_DROP(jsprin) before returning... PRBool ok = PR_FALSE; rv = sSecurityManager->CanExecuteScripts(mContext, principal, &ok); if (NS_FAILED(rv)) { JSPRINCIPALS_DROP(mContext, jsprin); return NS_ERROR_FAILURE; } // Push our JSContext on the current thread's context stack so JS called // from native code via XPConnect uses the right context. Do this whether // or not the SecurityManager said "ok", in order to simplify control flow // below where we pop before returning. nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv); if (NS_FAILED(rv) || NS_FAILED(stack->Push(mContext))) { JSPRINCIPALS_DROP(mContext, jsprin); return NS_ERROR_FAILURE; } // This context can be deleted unexpectedly if the JS closes the // owning window. nsCOMPtr kungFuDeathGrip(this); // The result of evaluation, used only if there were no errors. This need // not be a GC root currently, provided we run the GC only from the branch // callback or from ScriptEvaluated. TODO: use JS_Begin/EndRequest to keep // the GC from racing with JS execution on any thread. jsval val; if (ok) { JSVersion newVersion = JSVERSION_UNKNOWN; // SecurityManager said "ok", but don't execute if aVersion is specified // and unknown. Do execute with the default version (and avoid thrashing // the context's version) if aVersion is not specified. ok = (!aVersion || (newVersion = ::JS_StringToVersion(aVersion)) != JSVERSION_UNKNOWN); if (ok) { JSVersion oldVersion = JSVERSION_UNKNOWN; if (aVersion) oldVersion = ::JS_SetVersion(mContext, newVersion); mTerminationFuncArg = nsnull; mTerminationFunc = nsnull; ok = ::JS_EvaluateUCScriptForPrincipals(mContext, (JSObject *)aScopeObject, jsprin, (jschar*)(const PRUnichar*)PromiseFlatString(aScript).get(), aScript.Length(), aURL, aLineNo, &val); if (aVersion) ::JS_SetVersion(mContext, oldVersion); if (!ok) { // Tell XPConnect about any pending exceptions. This is needed // to avoid dropping JS exceptions in case we got here through // nested calls through XPConnect. NotifyXPCIfExceptionPending(mContext); } } } // Whew! Finally done with these manually ref-counted things. JSPRINCIPALS_DROP(mContext, jsprin); // If all went well, convert val to a string (XXXbe unless undefined?). if (ok) { if (aIsUndefined) { *aIsUndefined = JSVAL_IS_VOID(val); } *NS_STATIC_CAST(jsval*, aRetValue) = val; } else { if (aIsUndefined) { *aIsUndefined = PR_TRUE; } } // Pop here, after JS_ValueToString and any other possible evaluation. if (NS_FAILED(stack->Pop(nsnull))) rv = NS_ERROR_FAILURE; return rv; } // Helper function to convert a jsval to an nsAString, and set // exception flags if the conversion fails. static nsresult JSValueToAString(JSContext *cx, jsval val, nsAString *result, PRBool *isUndefined) { if (isUndefined) { *isUndefined = JSVAL_IS_VOID(val); } if (!result) { return NS_OK; } JSString* jsstring = ::JS_ValueToString(cx, val); if (jsstring) { result->Assign(NS_REINTERPRET_CAST(const PRUnichar*, ::JS_GetStringChars(jsstring)), ::JS_GetStringLength(jsstring)); } else { result->Truncate(); // We failed to convert val to a string. We're either OOM, or the // security manager denied access to .toString(), or somesuch, on // an object. Treat this case as if the result were undefined. if (isUndefined) { *isUndefined = PR_TRUE; } if (!::JS_IsExceptionPending(cx)) { // JS_ValueToString() returned null w/o an exception // pending. That means we're OOM. return NS_ERROR_OUT_OF_MEMORY; } // Tell XPConnect about any pending exceptions. This is needed to // avoid dropping JS exceptions in case we got here through nested // calls through XPConnect. NotifyXPCIfExceptionPending(cx); } return NS_OK; } nsresult nsJSContext::EvaluateString(const nsAString& aScript, void *aScopeObject, nsIPrincipal *aPrincipal, const char *aURL, PRUint32 aLineNo, const char* aVersion, nsAString& aRetValue, PRBool* aIsUndefined) { if (!mScriptsEnabled) { *aIsUndefined = PR_TRUE; aRetValue.Truncate(); return NS_OK; } nsresult rv; if (!aScopeObject) aScopeObject = ::JS_GetGlobalObject(mContext); // Safety first: get an object representing the script's principals, i.e., // the entities who signed this script, or the fully-qualified-domain-name // or "codebase" from which it was loaded. JSPrincipals *jsprin; nsCOMPtr principal = aPrincipal; if (aPrincipal) { aPrincipal->GetJSPrincipals(mContext, &jsprin); } else { nsCOMPtr objPrincipal = do_QueryInterface(GetGlobalObject(), &rv); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; rv = objPrincipal->GetPrincipal(getter_AddRefs(principal)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; principal->GetJSPrincipals(mContext, &jsprin); } // this context can be deleted unexpectedly if the JS closes the // owning window. nsCOMPtr kungFuDeathGrip(this); // From here on, we must JSPRINCIPALS_DROP(jsprin) before returning... PRBool ok = PR_FALSE; rv = sSecurityManager->CanExecuteScripts(mContext, principal, &ok); if (NS_FAILED(rv)) { JSPRINCIPALS_DROP(mContext, jsprin); return NS_ERROR_FAILURE; } // Push our JSContext on the current thread's context stack so JS called // from native code via XPConnect uses the right context. Do this whether // or not the SecurityManager said "ok", in order to simplify control flow // below where we pop before returning. nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv); if (NS_FAILED(rv) || NS_FAILED(stack->Push(mContext))) { JSPRINCIPALS_DROP(mContext, jsprin); return NS_ERROR_FAILURE; } // The result of evaluation, used only if there were no errors. This need // not be a GC root currently, provided we run the GC only from the branch // callback or from ScriptEvaluated. TODO: use JS_Begin/EndRequest to keep // the GC from racing with JS execution on any thread. jsval val; if (ok) { JSVersion newVersion = JSVERSION_UNKNOWN; // SecurityManager said "ok", but don't execute if aVersion is specified // and unknown. Do execute with the default version (and avoid thrashing // the context's version) if aVersion is not specified. ok = (!aVersion || (newVersion = ::JS_StringToVersion(aVersion)) != JSVERSION_UNKNOWN); if (ok) { JSVersion oldVersion = JSVERSION_UNKNOWN; if (aVersion) oldVersion = ::JS_SetVersion(mContext, newVersion); mTerminationFuncArg = nsnull; mTerminationFunc = nsnull; ok = ::JS_EvaluateUCScriptForPrincipals(mContext, (JSObject *)aScopeObject, jsprin, (jschar*)(const PRUnichar*)PromiseFlatString(aScript).get(), aScript.Length(), aURL, aLineNo, &val); if (aVersion) { ::JS_SetVersion(mContext, oldVersion); } if (!ok) { // Tell XPConnect about any pending exceptions. This is needed // to avoid dropping JS exceptions in case we got here through // nested calls through XPConnect. NotifyXPCIfExceptionPending(mContext); } } } // Whew! Finally done with these manually ref-counted things. JSPRINCIPALS_DROP(mContext, jsprin); // If all went well, convert val to a string (XXXbe unless undefined?). if (ok) { rv = JSValueToAString(mContext, val, &aRetValue, aIsUndefined); } else { if (aIsUndefined) { *aIsUndefined = PR_TRUE; } aRetValue.Truncate(); } ScriptEvaluated(PR_TRUE); // Pop here, after JS_ValueToString and any other possible evaluation. if (NS_FAILED(stack->Pop(nsnull))) rv = NS_ERROR_FAILURE; return rv; } nsresult nsJSContext::CompileScript(const PRUnichar* aText, PRInt32 aTextLength, void *aScopeObject, nsIPrincipal *aPrincipal, const char *aURL, PRUint32 aLineNo, const char* aVersion, void** aScriptObject) { nsresult rv; NS_ENSURE_ARG_POINTER(aPrincipal); if (!aScopeObject) aScopeObject = ::JS_GetGlobalObject(mContext); JSPrincipals *jsprin; aPrincipal->GetJSPrincipals(mContext, &jsprin); // From here on, we must JSPRINCIPALS_DROP(jsprin) before returning... PRBool ok = PR_FALSE; rv = sSecurityManager->CanExecuteScripts(mContext, aPrincipal, &ok); if (NS_FAILED(rv)) { JSPRINCIPALS_DROP(mContext, jsprin); return NS_ERROR_FAILURE; } *aScriptObject = nsnull; if (ok) { JSVersion newVersion = JSVERSION_UNKNOWN; // SecurityManager said "ok", but don't compile if aVersion is specified // and unknown. Do compile with the default version (and avoid thrashing // the context's version) if aVersion is not specified. if (!aVersion || (newVersion = ::JS_StringToVersion(aVersion)) != JSVERSION_UNKNOWN) { JSVersion oldVersion = JSVERSION_UNKNOWN; if (aVersion) oldVersion = ::JS_SetVersion(mContext, newVersion); JSScript* script = ::JS_CompileUCScriptForPrincipals(mContext, (JSObject*) aScopeObject, jsprin, (jschar*) aText, aTextLength, aURL, aLineNo); if (script) { *aScriptObject = (void*) ::JS_NewScriptObject(mContext, script); if (! *aScriptObject) { ::JS_DestroyScript(mContext, script); script = nsnull; } } if (!script) rv = NS_ERROR_OUT_OF_MEMORY; if (aVersion) ::JS_SetVersion(mContext, oldVersion); } } // Whew! Finally done with these manually ref-counted things. JSPRINCIPALS_DROP(mContext, jsprin); return rv; } nsresult nsJSContext::ExecuteScript(void* aScriptObject, void *aScopeObject, nsAString* aRetValue, PRBool* aIsUndefined) { if (!mScriptsEnabled) { if (aIsUndefined) { *aIsUndefined = PR_TRUE; } if (aRetValue) { aRetValue->Truncate(); } return NS_OK; } nsresult rv; if (!aScopeObject) aScopeObject = ::JS_GetGlobalObject(mContext); // Push our JSContext on our thread's context stack, in case native code // called from JS calls back into JS via XPConnect. nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv); if (NS_FAILED(rv) || NS_FAILED(stack->Push(mContext))) { return NS_ERROR_FAILURE; } // The result of evaluation, used only if there were no errors. This need // not be a GC root currently, provided we run the GC only from the branch // callback or from ScriptEvaluated. TODO: use JS_Begin/EndRequest to keep // the GC from racing with JS execution on any thread. jsval val; JSBool ok; mTerminationFuncArg = nsnull; mTerminationFunc = nsnull; ok = ::JS_ExecuteScript(mContext, (JSObject*) aScopeObject, (JSScript*) ::JS_GetPrivate(mContext, (JSObject*)aScriptObject), &val); if (ok) { // If all went well, convert val to a string (XXXbe unless undefined?). rv = JSValueToAString(mContext, val, aRetValue, aIsUndefined); } else { if (aIsUndefined) { *aIsUndefined = PR_TRUE; } if (aRetValue) { aRetValue->Truncate(); } // Tell XPConnect about any pending exceptions. This is needed to // avoid dropping JS exceptions in case we got here through nested // calls through XPConnect. NotifyXPCIfExceptionPending(mContext); } ScriptEvaluated(PR_TRUE); // Pop here, after JS_ValueToString and any other possible evaluation. if (NS_FAILED(stack->Pop(nsnull))) rv = NS_ERROR_FAILURE; return rv; } const char *gEventArgv[] = {"event"}; static inline const char * AtomToEventHandlerName(nsIAtom *aName) { const char *name; aName->GetUTF8String(&name); #ifdef DEBUG const char *cp; char c; for (cp = name; *cp != '\0'; ++cp) { c = *cp; NS_ASSERTION (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'), "non-ASCII non-alphabetic event handler name"); } #endif return name; } nsresult nsJSContext::CompileEventHandler(void *aTarget, nsIAtom *aName, const nsAString& aBody, const char *aURL, PRUint32 aLineNo, PRBool aShared, void** aHandler) { if (!sSecurityManager) { NS_ERROR("Huh, we need a script security manager to compile " "an event handler!"); return NS_ERROR_UNEXPECTED; } JSObject *target = (JSObject*)aTarget; JSPrincipals *jsprin = nsnull; if (target) { // Get the principal of the event target (the object principal), // don't get the principal of the global object in this context // since that opens up security exploits with delayed event // handler compilation on stale DOM objects (objects that live in // a document that has already been unloaded). nsCOMPtr prin; nsresult rv = sSecurityManager->GetObjectPrincipal(mContext, target, getter_AddRefs(prin)); NS_ENSURE_SUCCESS(rv, rv); prin->GetJSPrincipals(mContext, &jsprin); NS_ENSURE_TRUE(jsprin, NS_ERROR_NOT_AVAILABLE); } const char *charName = AtomToEventHandlerName(aName); JSFunction* fun = ::JS_CompileUCFunctionForPrincipals(mContext, target, jsprin, charName, 1, gEventArgv, (jschar*)(const PRUnichar*)PromiseFlatString(aBody).get(), aBody.Length(), aURL, aLineNo); if (jsprin) { JSPRINCIPALS_DROP(mContext, jsprin); } if (!fun) { return NS_ERROR_FAILURE; } JSObject *handler = ::JS_GetFunctionObject(fun); if (aHandler) *aHandler = (void*) handler; if (aShared) { /* Break scope link to avoid entraining shared compilation scope. */ ::JS_SetParent(mContext, handler, nsnull); } return NS_OK; } nsresult nsJSContext::CompileFunction(void* aTarget, const nsACString& aName, PRUint32 aArgCount, const char** aArgArray, const nsAString& aBody, const char* aURL, PRUint32 aLineNo, PRBool aShared, void** aFunctionObject) { JSPrincipals *jsprin = nsnull; nsIScriptGlobalObject *global = GetGlobalObject(); if (global) { // XXXbe why the two-step QI? speed up via a new GetGlobalObjectData func? nsCOMPtr globalData = do_QueryInterface(global); if (globalData) { nsCOMPtr prin; if (NS_FAILED(globalData->GetPrincipal(getter_AddRefs(prin)))) return NS_ERROR_FAILURE; prin->GetJSPrincipals(mContext, &jsprin); } } JSObject *target = (JSObject*)aTarget; JSFunction* fun = ::JS_CompileUCFunctionForPrincipals(mContext, target, jsprin, PromiseFlatCString(aName).get(), aArgCount, aArgArray, (jschar*)(const PRUnichar*)PromiseFlatString(aBody).get(), aBody.Length(), aURL, aLineNo); if (jsprin) JSPRINCIPALS_DROP(mContext, jsprin); if (!fun) return NS_ERROR_FAILURE; JSObject *handler = ::JS_GetFunctionObject(fun); if (aFunctionObject) *aFunctionObject = (void*) handler; // Prevent entraining just like CompileEventHandler does? if (aShared) { /* Break scope link to avoid entraining shared compilation scope. */ ::JS_SetParent(mContext, handler, nsnull); } return NS_OK; } nsresult nsJSContext::CallEventHandler(JSObject *aTarget, JSObject *aHandler, uintN argc, jsval *argv, jsval *rval) { *rval = JSVAL_VOID; if (!mScriptsEnabled) { //return NS_OK; } // This one's a lot easier than EvaluateString because we don't have to // hassle with principals: they're already compiled into the JS function. nsresult rv; nsCOMPtr stack = do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv); if (NS_FAILED(rv) || NS_FAILED(stack->Push(mContext))) return NS_ERROR_FAILURE; // this context can be deleted unexpectedly if the JS closes // the owning window. we ran into this problem specifically // when going through the "close window" key event handler // (that is, hitting ^W on Windows). the addref just below // prevents our untimely destruction. nsCOMPtr kungFuDeathGrip(this); mTerminationFuncArg = nsnull; mTerminationFunc = nsnull; // check if the event handler can be run on the object in question // rv = sSecurityManager->CheckFunctionAccess(mContext, aHandler, aTarget); if (NS_SUCCEEDED(rv)) { jsval funval = OBJECT_TO_JSVAL(aHandler); PRBool ok = ::JS_CallFunctionValue(mContext, aTarget, funval, argc, argv, rval); ScriptEvaluated(PR_TRUE); if (!ok) { // Tell XPConnect about any pending exceptions. This is needed // to avoid dropping JS exceptions in case we got here through // nested calls through XPConnect. NotifyXPCIfExceptionPending(mContext); // Don't pass back results from failed calls. *rval = JSVAL_VOID; // Tell the caller that the handler threw an error. rv = NS_ERROR_FAILURE; } } if (NS_FAILED(stack->Pop(nsnull))) return NS_ERROR_FAILURE; return rv; } nsresult nsJSContext::BindCompiledEventHandler(void *aTarget, nsIAtom *aName, void *aHandler) { const char *charName = AtomToEventHandlerName(aName); JSObject *funobj = (JSObject*) aHandler; JSObject *target = (JSObject*) aTarget; // Make sure the handler function is parented by its event target object if (funobj && ::JS_GetParent(mContext, funobj) != target) { funobj = ::JS_CloneFunctionObject(mContext, funobj, target); if (!funobj) return NS_ERROR_OUT_OF_MEMORY; } if (!::JS_DefineProperty(mContext, target, charName, OBJECT_TO_JSVAL(funobj), nsnull, nsnull, JSPROP_ENUMERATE | JSPROP_PERMANENT)) { return NS_ERROR_FAILURE; } return NS_OK; } void nsJSContext::SetDefaultLanguageVersion(const char* aVersion) { ::JS_SetVersion(mContext, ::JS_StringToVersion(aVersion)); } nsIScriptGlobalObject * nsJSContext::GetGlobalObject() { JSObject *global = ::JS_GetGlobalObject(mContext); if (!global) { NS_WARNING("Context has no global."); return nsnull; } JSClass *c = JS_GET_CLASS(mContext, global); if (!c || ((~c->flags) & (JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS))) { NS_WARNING("Global is not an nsISupports."); return nsnull; } nsCOMPtr sgo; nsISupports *priv = (nsISupports *)::JS_GetPrivate(mContext, global); nsCOMPtr wrapped_native = do_QueryInterface(priv); if (wrapped_native) { // The global object is a XPConnect wrapped native, the native in // the wrapper might be the nsIScriptGlobalObject nsCOMPtr native; wrapped_native->GetNative(getter_AddRefs(native)); NS_WARN_IF_FALSE(native, "XPConnect wrapped native doesn't wrap anything."); sgo = do_QueryInterface(native); } else { sgo = do_QueryInterface(priv); } // This'll return a pointer to something we're about to release, but // that's ok, the JS object will hold it alive long enough. return sgo; } void * nsJSContext::GetNativeContext() { return mContext; } nsresult nsJSContext::InitContext(nsIScriptGlobalObject *aGlobalObject) { if (!mContext) return NS_ERROR_OUT_OF_MEMORY; InvalidateContextAndWrapperCache(); nsresult rv; if (!gNameSpaceManager) { gNameSpaceManager = new nsScriptNameSpaceManager; NS_ENSURE_TRUE(gNameSpaceManager, NS_ERROR_OUT_OF_MEMORY); rv = gNameSpaceManager->Init(); NS_ENSURE_SUCCESS(rv, rv); } ::JS_SetErrorReporter(mContext, NS_ScriptErrorReporter); if (!aGlobalObject) { // If we don't get a global object then there's nothing more to do here. return NS_OK; } mIsInitialized = PR_FALSE; nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); NS_ENSURE_SUCCESS(rv, rv); JSObject *global = ::JS_GetGlobalObject(mContext); nsCOMPtr holder; // If there's already a global object in mContext we won't tell // XPConnect to wrap aGlobalObject since it's already wrapped. if (!global) { rv = xpc->InitClassesWithNewWrappedGlobal(mContext, aGlobalObject, NS_GET_IID(nsISupports), PR_FALSE, getter_AddRefs(holder)); NS_ENSURE_SUCCESS(rv, rv); } else { // If there's already a global object in mContext we're called // after ::JS_ClearScope() was called. We'll have to tell XPConnect // to re-initialize the global object to do things like define the // Components object on the global again. rv = xpc->InitClasses(mContext, global); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr ci(do_QueryInterface(aGlobalObject)); if (ci) { rv = xpc->WrapNative(mContext, global, aGlobalObject, NS_GET_IID(nsISupports), getter_AddRefs(holder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr wrapper(do_QueryInterface(holder)); NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); rv = wrapper->RefreshPrototype(); NS_ENSURE_SUCCESS(rv, rv); } } // Hold a strong reference to the wrapper for the global to avoid // rooting and unrooting the global object every time its AddRef() // or Release() methods are called mGlobalWrapperRef = holder; rv = InitClasses(); // this will complete global object initialization NS_ENSURE_SUCCESS(rv, rv); mIsInitialized = PR_TRUE; return rv; } nsresult nsJSContext::InitializeExternalClasses() { NS_ENSURE_TRUE(gNameSpaceManager, NS_ERROR_NOT_INITIALIZED); return gNameSpaceManager->InitForContext(this); } nsresult nsJSContext::InitializeLiveConnectClasses() { nsresult rv = NS_OK; #ifdef OJI nsCOMPtr jvmManager = do_GetService(nsIJVMManager::GetCID(), &rv); if (NS_SUCCEEDED(rv) && jvmManager) { PRBool javaEnabled = PR_FALSE; rv = jvmManager->GetJavaEnabled(&javaEnabled); if (NS_SUCCEEDED(rv) && javaEnabled) { nsCOMPtr liveConnectManager = do_QueryInterface(jvmManager); if (liveConnectManager) { rv = liveConnectManager->InitLiveConnectClasses(mContext, ::JS_GetGlobalObject(mContext)); } } } #endif /* OJI */ // return all is well until things are stable. return NS_OK; } static JSPropertySpec OptionsProperties[] = { {"strict", JSOPTION_STRICT, JSPROP_ENUMERATE | JSPROP_PERMANENT}, {"werror", JSOPTION_WERROR, JSPROP_ENUMERATE | JSPROP_PERMANENT}, {0} }; static JSBool JS_DLL_CALLBACK GetOptionsProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { if (JSVAL_IS_INT(id)) { uint32 optbit = (uint32) JSVAL_TO_INT(id); if ((optbit & (optbit - 1)) == 0 && optbit <= JSOPTION_WERROR) *vp = (JS_GetOptions(cx) & optbit) ? JSVAL_TRUE : JSVAL_FALSE; } return JS_TRUE; } static JSBool JS_DLL_CALLBACK SetOptionsProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { if (JSVAL_IS_INT(id)) { uint32 optbit = (uint32) JSVAL_TO_INT(id); // Don't let options other than strict and werror be set -- it would be // bad if web page script could clear JSOPTION_PRIVATE_IS_NSISUPPORTS! if ((optbit & (optbit - 1)) == 0 && optbit <= JSOPTION_WERROR) { JSBool optval; if (! ::JS_ValueToBoolean(cx, *vp, &optval)) return JS_FALSE; uint32 optset = ::JS_GetOptions(cx); if (optval) optset |= optbit; else optset &= ~optbit; ::JS_SetOptions(cx, optset); } } return JS_TRUE; } static JSClass OptionsClass = { "JSOptions", 0, JS_PropertyStub, JS_PropertyStub, GetOptionsProperty, SetOptionsProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; #ifdef NS_TRACE_MALLOC #include // XXX assume Linux if NS_TRACE_MALLOC #include #ifdef XP_UNIX #include #endif #ifdef XP_WIN32 #include #endif #include "nsTraceMalloc.h" static JSBool TraceMallocDisable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { NS_TraceMallocDisable(); return JS_TRUE; } static JSBool TraceMallocEnable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { NS_TraceMallocEnable(); return JS_TRUE; } static JSBool TraceMallocOpenLogFile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { int fd; JSString *str; char *filename; if (argc == 0) { fd = -1; } else { str = JS_ValueToString(cx, argv[0]); if (!str) return JS_FALSE; filename = JS_GetStringBytes(str); fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd < 0) { JS_ReportError(cx, "can't open %s: %s", filename, strerror(errno)); return JS_FALSE; } } *rval = INT_TO_JSVAL(fd); return JS_TRUE; } static JSBool TraceMallocChangeLogFD(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { int32 fd, oldfd; if (argc == 0) { oldfd = -1; } else { if (!JS_ValueToECMAInt32(cx, argv[0], &fd)) return JS_FALSE; oldfd = NS_TraceMallocChangeLogFD(fd); if (oldfd == -2) { JS_ReportOutOfMemory(cx); return JS_FALSE; } } *rval = INT_TO_JSVAL(oldfd); return JS_TRUE; } static JSBool TraceMallocCloseLogFD(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { int32 fd; if (argc == 0) return JS_TRUE; if (!JS_ValueToECMAInt32(cx, argv[0], &fd)) return JS_FALSE; NS_TraceMallocCloseLogFD((int) fd); return JS_TRUE; } static JSBool TraceMallocLogTimestamp(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str; const char *caption; str = JS_ValueToString(cx, argv[0]); if (!str) return JS_FALSE; caption = JS_GetStringBytes(str); NS_TraceMallocLogTimestamp(caption); return JS_TRUE; } static JSBool TraceMallocDumpAllocations(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str; const char *pathname; str = JS_ValueToString(cx, argv[0]); if (!str) return JS_FALSE; pathname = JS_GetStringBytes(str); if (NS_TraceMallocDumpAllocations(pathname) < 0) { JS_ReportError(cx, "can't dump to %s: %s", pathname, strerror(errno)); return JS_FALSE; } return JS_TRUE; } static JSFunctionSpec TraceMallocFunctions[] = { {"TraceMallocDisable", TraceMallocDisable, 0, 0, 0}, {"TraceMallocEnable", TraceMallocEnable, 0, 0, 0}, {"TraceMallocOpenLogFile", TraceMallocOpenLogFile, 1, 0, 0}, {"TraceMallocChangeLogFD", TraceMallocChangeLogFD, 1, 0, 0}, {"TraceMallocCloseLogFD", TraceMallocCloseLogFD, 1, 0, 0}, {"TraceMallocLogTimestamp", TraceMallocLogTimestamp, 1, 0, 0}, {"TraceMallocDumpAllocations", TraceMallocDumpAllocations, 1, 0, 0}, {nsnull, nsnull, 0, 0, 0} }; #endif /* NS_TRACE_MALLOC */ nsresult nsJSContext::InitClasses() { nsresult rv = NS_OK; JSObject *globalObj = ::JS_GetGlobalObject(mContext); rv = InitializeExternalClasses(); NS_ENSURE_SUCCESS(rv, rv); rv = InitializeLiveConnectClasses(); NS_ENSURE_SUCCESS(rv, rv); rv = nsDOMClassInfo::InitDOMJSClass(mContext, globalObj); NS_ENSURE_SUCCESS(rv, rv); // Initialize the options object and set default options in mContext JSObject *optionsObj = ::JS_DefineObject(mContext, globalObj, "_options", &OptionsClass, nsnull, 0); if (optionsObj && ::JS_DefineProperties(mContext, optionsObj, OptionsProperties)) { ::JS_SetOptions(mContext, mDefaultJSOptions); } else { rv = NS_ERROR_FAILURE; } #ifdef NS_TRACE_MALLOC // Attempt to initialize TraceMalloc functions ::JS_DefineFunctions(mContext, globalObj, TraceMallocFunctions); #endif return rv; } PRBool nsJSContext::IsContextInitialized() { return mIsInitialized; } void nsJSContext::GC() { FireGCTimer(); } void nsJSContext::ScriptEvaluated(PRBool aTerminated) { if (aTerminated && mTerminationFunc) { (*mTerminationFunc)(mTerminationFuncArg); mTerminationFuncArg = nsnull; mTerminationFunc = nsnull; } mNumEvaluations++; if (mNumEvaluations > 20) { mNumEvaluations = 0; ::JS_MaybeGC(mContext); } mBranchCallbackCount = 0; mBranchCallbackTime = LL_ZERO; } void nsJSContext::SetOwner(nsIScriptContextOwner* owner) { // The owner should not be addrefed!! We'll be told // when the owner goes away. mOwner = owner; } nsIScriptContextOwner * nsJSContext::GetOwner() { return mOwner; } void nsJSContext::SetTerminationFunction(nsScriptTerminationFunc aFunc, nsISupports* aRef) { mTerminationFunc = aFunc; mTerminationFuncArg = aRef; } PRBool nsJSContext::GetScriptsEnabled() { return mScriptsEnabled; } void nsJSContext::SetScriptsEnabled(PRBool aEnabled, PRBool aFireTimeouts) { mScriptsEnabled = aEnabled; nsIScriptGlobalObject *global = GetGlobalObject(); if (global) { global->SetScriptsEnabled(aEnabled, aFireTimeouts); } } PRBool nsJSContext::GetProcessingScriptTag() { return mProcessingScriptTag; } void nsJSContext::SetProcessingScriptTag(PRBool aFlag) { mProcessingScriptTag = aFlag; } void nsJSContext::SetGCOnDestruction(PRBool aGCOnDestruction) { mGCOnDestruction = aGCOnDestruction; } NS_IMETHODIMP nsJSContext::ScriptExecuted() { ScriptEvaluated(PR_FALSE); return NS_OK; } NS_IMETHODIMP nsJSContext::Notify(nsITimer *timer) { NS_ASSERTION(mContext, "No context in nsJSContext::Notify()!"); ::JS_GC(mContext); sReadyForGC = PR_TRUE; NS_RELEASE(sGCTimer); return NS_OK; } void nsJSContext::FireGCTimer() { if (sGCTimer) { // There's already a timer for GC'ing, just clear newborn roots // and return ::JS_ClearNewbornRoots(mContext); return; } nsComponentManager::CreateInstance("@mozilla.org/timer;1", nsnull, NS_GET_IID(nsITimer), (void **)&sGCTimer); if (!sGCTimer) { NS_WARNING("Failed to create timer"); ::JS_GC(mContext); return; } static PRBool first = PR_TRUE; sGCTimer->InitWithCallback(this, first ? NS_FIRST_GC_DELAY : NS_GC_DELAY, nsITimer::TYPE_ONE_SHOT); first = PR_FALSE; } static JSBool JS_DLL_CALLBACK DOMGCCallback(JSContext *cx, JSGCStatus status) { if (status == JSGC_BEGIN && PR_GetCurrentThread() != gDOMThread) return JS_FALSE; return gOldJSGCCallback ? gOldJSGCCallback(cx, status) : JS_TRUE; } // static nsresult nsJSEnvironment::Init() { static PRBool isInitialized; if (isInitialized) { return NS_OK; } nsresult rv = CallGetService(kJSRuntimeServiceContractID, &sRuntimeService); // get the JSRuntime from the runtime svc, if possible NS_ENSURE_SUCCESS(rv, rv); rv = sRuntimeService->GetRuntime(&sRuntime); NS_ENSURE_SUCCESS(rv, rv); gDOMThread = PR_GetCurrentThread(); #ifdef DEBUG // Let's make sure that our main thread is the same as the xpcom main thread. { nsCOMPtr t; nsresult rv; PRThread* mainThread; rv = nsIThread::GetMainThread(getter_AddRefs(t)); NS_ASSERTION(NS_SUCCEEDED(rv) && t, "bad"); rv = t->GetPRThread(&mainThread); NS_ASSERTION(NS_SUCCEEDED(rv) && mainThread == gDOMThread, "bad"); } #endif NS_ASSERTION(!gOldJSGCCallback, "nsJSEnvironment initialized more than once"); gOldJSGCCallback = ::JS_SetGCCallbackRT(sRuntime, DOMGCCallback); // Set these global xpconnect options... nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); if (NS_SUCCEEDED(rv)) { xpc->SetCollectGarbageOnMainThreadOnly(PR_TRUE); xpc->SetDeferReleasesUntilAfterGarbageCollection(PR_TRUE); } else { NS_WARNING("Failed to get XPConnect service!"); } #ifdef OJI // Initialize LiveConnect. XXXbe use contractid rather than GetCID // NOTE: LiveConnect is optional so initialisation will still succeed // even if the service is not present. nsCOMPtr manager = do_GetService(nsIJVMManager::GetCID()); // Should the JVM manager perhaps define methods for starting up // LiveConnect? if (manager) { PRBool started = PR_FALSE; rv = manager->StartupLiveConnect(sRuntime, started); } #endif /* OJI */ // Initialize limit on script run time to 5 seconds PRInt32 maxtime = 5; nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { PRInt32 time; if (NS_SUCCEEDED(prefs->GetIntPref("dom.max_script_run_time", &time))) { // Force the default for unreasonable values if (time > 0) maxtime = time; } } PRTime usec_per_sec; LL_I2L(usec_per_sec, PR_USEC_PER_SEC); LL_I2L(sMaxScriptRunTime, maxtime); LL_MUL(sMaxScriptRunTime, sMaxScriptRunTime, usec_per_sec); rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &sSecurityManager); isInitialized = NS_SUCCEEDED(rv); return rv; } // static void nsJSEnvironment::ShutDown() { if (sGCTimer) { // We're being shut down, if we have a GC timer scheduled, cancel // it. The DOM factory will do one final GC once it's shut down. sGCTimer->Cancel(); NS_RELEASE(sGCTimer); } delete gNameSpaceManager; gNameSpaceManager = nsnull; if (!sContextCount) { // We're being shutdown, and there are no more contexts // alive, release the JS runtime service and the security manager. NS_IF_RELEASE(sRuntimeService); NS_IF_RELEASE(sSecurityManager); NS_IF_RELEASE(gCollation); } sDidShutdown = PR_TRUE; } // static nsresult nsJSEnvironment::CreateNewContext(nsIScriptContext **aContext) { *aContext = new nsJSContext(sRuntime); NS_ENSURE_TRUE(*aContext, NS_ERROR_OUT_OF_MEMORY); NS_ADDREF(*aContext); return NS_OK; } nsresult NS_CreateScriptContext(nsIScriptGlobalObject *aGlobal, nsIScriptContext **aContext) { nsresult rv = nsJSEnvironment::Init(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr scriptContext; rv = nsJSEnvironment::CreateNewContext(getter_AddRefs(scriptContext)); NS_ENSURE_SUCCESS(rv, rv); // Bind the script context and the global object rv = scriptContext->InitContext(aGlobal); NS_ENSURE_SUCCESS(rv, rv); if (aGlobal) { aGlobal->SetContext(scriptContext); } *aContext = scriptContext; NS_ADDREF(*aContext); return rv; }