michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * This is not a generated file. It contains common utility functions michael@0: * invoked from the JavaScript code generated from IDL interfaces. michael@0: * The goal of the utility functions is to cut down on the size of michael@0: * the generated code itself. michael@0: */ michael@0: michael@0: #include "nsJSUtils.h" michael@0: #include "jsapi.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "jsfriendapi.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsDOMJSUtils.h" // for GetScriptContextFromJSContext michael@0: #include "nsJSPrincipals.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsGlobalWindow.h" michael@0: michael@0: bool michael@0: nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename, michael@0: uint32_t* aLineno) michael@0: { michael@0: JS::AutoFilename filename; michael@0: unsigned lineno = 0; michael@0: michael@0: if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) { michael@0: return false; michael@0: } michael@0: michael@0: *aFilename = filename.get(); michael@0: *aLineno = lineno; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsIScriptGlobalObject * michael@0: nsJSUtils::GetStaticScriptGlobal(JSObject* aObj) michael@0: { michael@0: if (!aObj) michael@0: return nullptr; michael@0: return xpc::WindowGlobalOrNull(aObj); michael@0: } michael@0: michael@0: nsIScriptContext * michael@0: nsJSUtils::GetStaticScriptContext(JSObject* aObj) michael@0: { michael@0: nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj); michael@0: if (!nativeGlobal) michael@0: return nullptr; michael@0: michael@0: return nativeGlobal->GetScriptContext(); michael@0: } michael@0: michael@0: nsIScriptGlobalObject * michael@0: nsJSUtils::GetDynamicScriptGlobal(JSContext* aContext) michael@0: { michael@0: nsIScriptContext *scriptCX = GetDynamicScriptContext(aContext); michael@0: if (!scriptCX) michael@0: return nullptr; michael@0: return scriptCX->GetGlobalObject(); michael@0: } michael@0: michael@0: nsIScriptContext * michael@0: nsJSUtils::GetDynamicScriptContext(JSContext *aContext) michael@0: { michael@0: return GetScriptContextFromJSContext(aContext); michael@0: } michael@0: michael@0: uint64_t michael@0: nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext) michael@0: { michael@0: if (!aContext) michael@0: return 0; michael@0: michael@0: uint64_t innerWindowID = 0; michael@0: michael@0: JSObject *jsGlobal = JS::CurrentGlobalOrNull(aContext); michael@0: if (jsGlobal) { michael@0: nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(jsGlobal); michael@0: if (scriptGlobal) { michael@0: nsCOMPtr win = do_QueryInterface(scriptGlobal); michael@0: if (win) michael@0: innerWindowID = win->WindowID(); michael@0: } michael@0: } michael@0: michael@0: return innerWindowID; michael@0: } michael@0: michael@0: void michael@0: nsJSUtils::ReportPendingException(JSContext *aContext) michael@0: { michael@0: if (JS_IsExceptionPending(aContext)) { michael@0: bool saved = JS_SaveFrameChain(aContext); michael@0: { michael@0: // JS_SaveFrameChain set the compartment of aContext to null, so we need michael@0: // to enter a compartment. The question is, which one? We don't want to michael@0: // enter the original compartment of aContext (or the compartment of the michael@0: // current exception on aContext, for that matter) because when we michael@0: // JS_ReportPendingException the JS engine can try to duck-type the michael@0: // exception and produce a JSErrorReport. It will then pass that michael@0: // JSErrorReport to the error reporter on aContext, which might expose michael@0: // information from it to script via onerror handlers. So it's very michael@0: // important that the duck typing happen in the same compartment as the michael@0: // onerror handler. In practice, that's the compartment of the window (or michael@0: // otherwise default global) of aContext, so use that here. michael@0: nsIScriptContext* scx = GetScriptContextFromJSContext(aContext); michael@0: JS::Rooted scope(aContext); michael@0: scope = scx ? scx->GetWindowProxy() michael@0: : js::DefaultObjectForContextOrNull(aContext); michael@0: if (!scope) { michael@0: // The SafeJSContext has no default object associated with it. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext()); michael@0: scope = xpc::GetSafeJSContextGlobal(); michael@0: } michael@0: JSAutoCompartment ac(aContext, scope); michael@0: JS_ReportPendingException(aContext); michael@0: } michael@0: if (saved) { michael@0: JS_RestoreFrameChain(aContext); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsJSUtils::CompileFunction(JSContext* aCx, michael@0: JS::Handle aTarget, michael@0: JS::CompileOptions& aOptions, michael@0: const nsACString& aName, michael@0: uint32_t aArgCount, michael@0: const char** aArgArray, michael@0: const nsAString& aBody, michael@0: JSObject** aFunctionObject) michael@0: { michael@0: MOZ_ASSERT(js::GetEnterCompartmentDepth(aCx) > 0); michael@0: MOZ_ASSERT_IF(aTarget, js::IsObjectInContextCompartment(aTarget, aCx)); michael@0: MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN); michael@0: mozilla::DebugOnly ctx = GetScriptContextFromJSContext(aCx); michael@0: MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized()); michael@0: michael@0: // Do the junk Gecko is supposed to do before calling into JSAPI. michael@0: if (aTarget) { michael@0: JS::ExposeObjectToActiveJS(aTarget); michael@0: } michael@0: michael@0: // Compile. michael@0: JSFunction* fun = JS::CompileFunction(aCx, aTarget, aOptions, michael@0: PromiseFlatCString(aName).get(), michael@0: aArgCount, aArgArray, michael@0: PromiseFlatString(aBody).get(), michael@0: aBody.Length()); michael@0: if (!fun) { michael@0: ReportPendingException(aCx); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *aFunctionObject = JS_GetFunctionObject(fun); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJSUtils::EvaluateString(JSContext* aCx, michael@0: const nsAString& aScript, michael@0: JS::Handle aScopeObject, michael@0: JS::CompileOptions& aCompileOptions, michael@0: const EvaluateOptions& aEvaluateOptions, michael@0: JS::MutableHandle aRetValue, michael@0: void **aOffThreadToken) michael@0: { michael@0: const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); michael@0: JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), michael@0: JS::SourceBufferHolder::NoOwnership); michael@0: return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions, michael@0: aEvaluateOptions, aRetValue, aOffThreadToken); michael@0: } michael@0: michael@0: nsresult michael@0: nsJSUtils::EvaluateString(JSContext* aCx, michael@0: JS::SourceBufferHolder& aSrcBuf, michael@0: JS::Handle aScopeObject, michael@0: JS::CompileOptions& aCompileOptions, michael@0: const EvaluateOptions& aEvaluateOptions, michael@0: JS::MutableHandle aRetValue, michael@0: void **aOffThreadToken) michael@0: { michael@0: PROFILER_LABEL("JS", "EvaluateString"); michael@0: MOZ_ASSERT_IF(aCompileOptions.versionSet, michael@0: aCompileOptions.version != JSVERSION_UNKNOWN); michael@0: MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult); michael@0: MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult); michael@0: MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); michael@0: MOZ_ASSERT(aSrcBuf.get()); michael@0: michael@0: // Unfortunately, the JS engine actually compiles scripts with a return value michael@0: // in a different, less efficient way. Furthermore, it can't JIT them in many michael@0: // cases. So we need to be explicitly told whether the caller cares about the michael@0: // return value. Callers can do this by calling the other overload of michael@0: // EvaluateString() which calls this function with aEvaluateOptions.needResult michael@0: // set to false. michael@0: aRetValue.setUndefined(); michael@0: michael@0: JS::ExposeObjectToActiveJS(aScopeObject); michael@0: nsAutoMicroTask mt; michael@0: nsresult rv = NS_OK; michael@0: michael@0: bool ok = false; michael@0: nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); michael@0: NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK); michael@0: michael@0: mozilla::Maybe dontReport; michael@0: if (!aEvaluateOptions.reportUncaught) { michael@0: // We need to prevent AutoLastFrameCheck from reporting and clearing michael@0: // any pending exceptions. michael@0: dontReport.construct(aCx); michael@0: } michael@0: michael@0: // Scope the JSAutoCompartment so that we can later wrap the return value michael@0: // into the caller's cx. michael@0: { michael@0: JSAutoCompartment ac(aCx, aScopeObject); michael@0: michael@0: JS::Rooted rootedScope(aCx, aScopeObject); michael@0: if (aOffThreadToken) { michael@0: JS::Rooted michael@0: script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken)); michael@0: *aOffThreadToken = nullptr; // Mark the token as having been finished. michael@0: if (script) { michael@0: if (aEvaluateOptions.needResult) { michael@0: ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue); michael@0: } else { michael@0: ok = JS_ExecuteScript(aCx, rootedScope, script); michael@0: } michael@0: } else { michael@0: ok = false; michael@0: } michael@0: } else { michael@0: if (aEvaluateOptions.needResult) { michael@0: ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, michael@0: aSrcBuf, aRetValue); michael@0: } else { michael@0: ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, michael@0: aSrcBuf); michael@0: } michael@0: } michael@0: michael@0: if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) { michael@0: JS::Rooted value(aCx, aRetValue); michael@0: JSString* str = JS::ToString(aCx, value); michael@0: ok = !!str; michael@0: aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue()); michael@0: } michael@0: } michael@0: michael@0: if (!ok) { michael@0: if (aEvaluateOptions.reportUncaught) { michael@0: ReportPendingException(aCx); michael@0: if (aEvaluateOptions.needResult) { michael@0: aRetValue.setUndefined(); michael@0: } michael@0: } else { michael@0: rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE michael@0: : NS_ERROR_OUT_OF_MEMORY; michael@0: JS::Rooted exn(aCx); michael@0: JS_GetPendingException(aCx, &exn); michael@0: if (aEvaluateOptions.needResult) { michael@0: aRetValue.set(exn); michael@0: } michael@0: JS_ClearPendingException(aCx); michael@0: } michael@0: } michael@0: michael@0: // Wrap the return value into whatever compartment aCx was in. michael@0: if (aEvaluateOptions.needResult) { michael@0: JS::Rooted v(aCx, aRetValue); michael@0: if (!JS_WrapValue(aCx, &v)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: aRetValue.set(v); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsJSUtils::EvaluateString(JSContext* aCx, michael@0: const nsAString& aScript, michael@0: JS::Handle aScopeObject, michael@0: JS::CompileOptions& aCompileOptions, michael@0: void **aOffThreadToken) michael@0: { michael@0: EvaluateOptions options; michael@0: options.setNeedResult(false); michael@0: JS::RootedValue unused(aCx); michael@0: return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions, michael@0: options, &unused, aOffThreadToken); michael@0: } michael@0: michael@0: nsresult michael@0: nsJSUtils::EvaluateString(JSContext* aCx, michael@0: JS::SourceBufferHolder& aSrcBuf, michael@0: JS::Handle aScopeObject, michael@0: JS::CompileOptions& aCompileOptions, michael@0: void **aOffThreadToken) michael@0: { michael@0: EvaluateOptions options; michael@0: options.setNeedResult(false); michael@0: JS::RootedValue unused(aCx); michael@0: return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions, michael@0: options, &unused, aOffThreadToken); michael@0: } michael@0: michael@0: // michael@0: // nsDOMJSUtils.h michael@0: // michael@0: michael@0: JSObject* GetDefaultScopeFromJSContext(JSContext *cx) michael@0: { michael@0: // DOM JSContexts don't store their default compartment object on michael@0: // the cx, so in those cases we need to fetch it via the scx michael@0: // instead. michael@0: nsIScriptContext *scx = GetScriptContextFromJSContext(cx); michael@0: if (scx) { michael@0: return scx->GetWindowProxy(); michael@0: } michael@0: return js::DefaultObjectForContextOrNull(cx); michael@0: }