1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/base/nsJSUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,338 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sw=2 et tw=78: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/** 1.11 + * This is not a generated file. It contains common utility functions 1.12 + * invoked from the JavaScript code generated from IDL interfaces. 1.13 + * The goal of the utility functions is to cut down on the size of 1.14 + * the generated code itself. 1.15 + */ 1.16 + 1.17 +#include "nsJSUtils.h" 1.18 +#include "jsapi.h" 1.19 +#include "js/OldDebugAPI.h" 1.20 +#include "jsfriendapi.h" 1.21 +#include "nsIScriptContext.h" 1.22 +#include "nsIScriptGlobalObject.h" 1.23 +#include "nsIXPConnect.h" 1.24 +#include "nsCOMPtr.h" 1.25 +#include "nsIScriptSecurityManager.h" 1.26 +#include "nsPIDOMWindow.h" 1.27 +#include "GeckoProfiler.h" 1.28 +#include "nsDOMJSUtils.h" // for GetScriptContextFromJSContext 1.29 +#include "nsJSPrincipals.h" 1.30 +#include "xpcpublic.h" 1.31 +#include "nsContentUtils.h" 1.32 +#include "nsGlobalWindow.h" 1.33 + 1.34 +bool 1.35 +nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename, 1.36 + uint32_t* aLineno) 1.37 +{ 1.38 + JS::AutoFilename filename; 1.39 + unsigned lineno = 0; 1.40 + 1.41 + if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) { 1.42 + return false; 1.43 + } 1.44 + 1.45 + *aFilename = filename.get(); 1.46 + *aLineno = lineno; 1.47 + 1.48 + return true; 1.49 +} 1.50 + 1.51 +nsIScriptGlobalObject * 1.52 +nsJSUtils::GetStaticScriptGlobal(JSObject* aObj) 1.53 +{ 1.54 + if (!aObj) 1.55 + return nullptr; 1.56 + return xpc::WindowGlobalOrNull(aObj); 1.57 +} 1.58 + 1.59 +nsIScriptContext * 1.60 +nsJSUtils::GetStaticScriptContext(JSObject* aObj) 1.61 +{ 1.62 + nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj); 1.63 + if (!nativeGlobal) 1.64 + return nullptr; 1.65 + 1.66 + return nativeGlobal->GetScriptContext(); 1.67 +} 1.68 + 1.69 +nsIScriptGlobalObject * 1.70 +nsJSUtils::GetDynamicScriptGlobal(JSContext* aContext) 1.71 +{ 1.72 + nsIScriptContext *scriptCX = GetDynamicScriptContext(aContext); 1.73 + if (!scriptCX) 1.74 + return nullptr; 1.75 + return scriptCX->GetGlobalObject(); 1.76 +} 1.77 + 1.78 +nsIScriptContext * 1.79 +nsJSUtils::GetDynamicScriptContext(JSContext *aContext) 1.80 +{ 1.81 + return GetScriptContextFromJSContext(aContext); 1.82 +} 1.83 + 1.84 +uint64_t 1.85 +nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext) 1.86 +{ 1.87 + if (!aContext) 1.88 + return 0; 1.89 + 1.90 + uint64_t innerWindowID = 0; 1.91 + 1.92 + JSObject *jsGlobal = JS::CurrentGlobalOrNull(aContext); 1.93 + if (jsGlobal) { 1.94 + nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(jsGlobal); 1.95 + if (scriptGlobal) { 1.96 + nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(scriptGlobal); 1.97 + if (win) 1.98 + innerWindowID = win->WindowID(); 1.99 + } 1.100 + } 1.101 + 1.102 + return innerWindowID; 1.103 +} 1.104 + 1.105 +void 1.106 +nsJSUtils::ReportPendingException(JSContext *aContext) 1.107 +{ 1.108 + if (JS_IsExceptionPending(aContext)) { 1.109 + bool saved = JS_SaveFrameChain(aContext); 1.110 + { 1.111 + // JS_SaveFrameChain set the compartment of aContext to null, so we need 1.112 + // to enter a compartment. The question is, which one? We don't want to 1.113 + // enter the original compartment of aContext (or the compartment of the 1.114 + // current exception on aContext, for that matter) because when we 1.115 + // JS_ReportPendingException the JS engine can try to duck-type the 1.116 + // exception and produce a JSErrorReport. It will then pass that 1.117 + // JSErrorReport to the error reporter on aContext, which might expose 1.118 + // information from it to script via onerror handlers. So it's very 1.119 + // important that the duck typing happen in the same compartment as the 1.120 + // onerror handler. In practice, that's the compartment of the window (or 1.121 + // otherwise default global) of aContext, so use that here. 1.122 + nsIScriptContext* scx = GetScriptContextFromJSContext(aContext); 1.123 + JS::Rooted<JSObject*> scope(aContext); 1.124 + scope = scx ? scx->GetWindowProxy() 1.125 + : js::DefaultObjectForContextOrNull(aContext); 1.126 + if (!scope) { 1.127 + // The SafeJSContext has no default object associated with it. 1.128 + MOZ_ASSERT(NS_IsMainThread()); 1.129 + MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext()); 1.130 + scope = xpc::GetSafeJSContextGlobal(); 1.131 + } 1.132 + JSAutoCompartment ac(aContext, scope); 1.133 + JS_ReportPendingException(aContext); 1.134 + } 1.135 + if (saved) { 1.136 + JS_RestoreFrameChain(aContext); 1.137 + } 1.138 + } 1.139 +} 1.140 + 1.141 +nsresult 1.142 +nsJSUtils::CompileFunction(JSContext* aCx, 1.143 + JS::Handle<JSObject*> aTarget, 1.144 + JS::CompileOptions& aOptions, 1.145 + const nsACString& aName, 1.146 + uint32_t aArgCount, 1.147 + const char** aArgArray, 1.148 + const nsAString& aBody, 1.149 + JSObject** aFunctionObject) 1.150 +{ 1.151 + MOZ_ASSERT(js::GetEnterCompartmentDepth(aCx) > 0); 1.152 + MOZ_ASSERT_IF(aTarget, js::IsObjectInContextCompartment(aTarget, aCx)); 1.153 + MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN); 1.154 + mozilla::DebugOnly<nsIScriptContext*> ctx = GetScriptContextFromJSContext(aCx); 1.155 + MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized()); 1.156 + 1.157 + // Do the junk Gecko is supposed to do before calling into JSAPI. 1.158 + if (aTarget) { 1.159 + JS::ExposeObjectToActiveJS(aTarget); 1.160 + } 1.161 + 1.162 + // Compile. 1.163 + JSFunction* fun = JS::CompileFunction(aCx, aTarget, aOptions, 1.164 + PromiseFlatCString(aName).get(), 1.165 + aArgCount, aArgArray, 1.166 + PromiseFlatString(aBody).get(), 1.167 + aBody.Length()); 1.168 + if (!fun) { 1.169 + ReportPendingException(aCx); 1.170 + return NS_ERROR_FAILURE; 1.171 + } 1.172 + 1.173 + *aFunctionObject = JS_GetFunctionObject(fun); 1.174 + return NS_OK; 1.175 +} 1.176 + 1.177 +nsresult 1.178 +nsJSUtils::EvaluateString(JSContext* aCx, 1.179 + const nsAString& aScript, 1.180 + JS::Handle<JSObject*> aScopeObject, 1.181 + JS::CompileOptions& aCompileOptions, 1.182 + const EvaluateOptions& aEvaluateOptions, 1.183 + JS::MutableHandle<JS::Value> aRetValue, 1.184 + void **aOffThreadToken) 1.185 +{ 1.186 + const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); 1.187 + JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), 1.188 + JS::SourceBufferHolder::NoOwnership); 1.189 + return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions, 1.190 + aEvaluateOptions, aRetValue, aOffThreadToken); 1.191 +} 1.192 + 1.193 +nsresult 1.194 +nsJSUtils::EvaluateString(JSContext* aCx, 1.195 + JS::SourceBufferHolder& aSrcBuf, 1.196 + JS::Handle<JSObject*> aScopeObject, 1.197 + JS::CompileOptions& aCompileOptions, 1.198 + const EvaluateOptions& aEvaluateOptions, 1.199 + JS::MutableHandle<JS::Value> aRetValue, 1.200 + void **aOffThreadToken) 1.201 +{ 1.202 + PROFILER_LABEL("JS", "EvaluateString"); 1.203 + MOZ_ASSERT_IF(aCompileOptions.versionSet, 1.204 + aCompileOptions.version != JSVERSION_UNKNOWN); 1.205 + MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult); 1.206 + MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult); 1.207 + MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); 1.208 + MOZ_ASSERT(aSrcBuf.get()); 1.209 + 1.210 + // Unfortunately, the JS engine actually compiles scripts with a return value 1.211 + // in a different, less efficient way. Furthermore, it can't JIT them in many 1.212 + // cases. So we need to be explicitly told whether the caller cares about the 1.213 + // return value. Callers can do this by calling the other overload of 1.214 + // EvaluateString() which calls this function with aEvaluateOptions.needResult 1.215 + // set to false. 1.216 + aRetValue.setUndefined(); 1.217 + 1.218 + JS::ExposeObjectToActiveJS(aScopeObject); 1.219 + nsAutoMicroTask mt; 1.220 + nsresult rv = NS_OK; 1.221 + 1.222 + bool ok = false; 1.223 + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 1.224 + NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK); 1.225 + 1.226 + mozilla::Maybe<AutoDontReportUncaught> dontReport; 1.227 + if (!aEvaluateOptions.reportUncaught) { 1.228 + // We need to prevent AutoLastFrameCheck from reporting and clearing 1.229 + // any pending exceptions. 1.230 + dontReport.construct(aCx); 1.231 + } 1.232 + 1.233 + // Scope the JSAutoCompartment so that we can later wrap the return value 1.234 + // into the caller's cx. 1.235 + { 1.236 + JSAutoCompartment ac(aCx, aScopeObject); 1.237 + 1.238 + JS::Rooted<JSObject*> rootedScope(aCx, aScopeObject); 1.239 + if (aOffThreadToken) { 1.240 + JS::Rooted<JSScript*> 1.241 + script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken)); 1.242 + *aOffThreadToken = nullptr; // Mark the token as having been finished. 1.243 + if (script) { 1.244 + if (aEvaluateOptions.needResult) { 1.245 + ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue); 1.246 + } else { 1.247 + ok = JS_ExecuteScript(aCx, rootedScope, script); 1.248 + } 1.249 + } else { 1.250 + ok = false; 1.251 + } 1.252 + } else { 1.253 + if (aEvaluateOptions.needResult) { 1.254 + ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, 1.255 + aSrcBuf, aRetValue); 1.256 + } else { 1.257 + ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, 1.258 + aSrcBuf); 1.259 + } 1.260 + } 1.261 + 1.262 + if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) { 1.263 + JS::Rooted<JS::Value> value(aCx, aRetValue); 1.264 + JSString* str = JS::ToString(aCx, value); 1.265 + ok = !!str; 1.266 + aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue()); 1.267 + } 1.268 + } 1.269 + 1.270 + if (!ok) { 1.271 + if (aEvaluateOptions.reportUncaught) { 1.272 + ReportPendingException(aCx); 1.273 + if (aEvaluateOptions.needResult) { 1.274 + aRetValue.setUndefined(); 1.275 + } 1.276 + } else { 1.277 + rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE 1.278 + : NS_ERROR_OUT_OF_MEMORY; 1.279 + JS::Rooted<JS::Value> exn(aCx); 1.280 + JS_GetPendingException(aCx, &exn); 1.281 + if (aEvaluateOptions.needResult) { 1.282 + aRetValue.set(exn); 1.283 + } 1.284 + JS_ClearPendingException(aCx); 1.285 + } 1.286 + } 1.287 + 1.288 + // Wrap the return value into whatever compartment aCx was in. 1.289 + if (aEvaluateOptions.needResult) { 1.290 + JS::Rooted<JS::Value> v(aCx, aRetValue); 1.291 + if (!JS_WrapValue(aCx, &v)) { 1.292 + return NS_ERROR_OUT_OF_MEMORY; 1.293 + } 1.294 + aRetValue.set(v); 1.295 + } 1.296 + return rv; 1.297 +} 1.298 + 1.299 +nsresult 1.300 +nsJSUtils::EvaluateString(JSContext* aCx, 1.301 + const nsAString& aScript, 1.302 + JS::Handle<JSObject*> aScopeObject, 1.303 + JS::CompileOptions& aCompileOptions, 1.304 + void **aOffThreadToken) 1.305 +{ 1.306 + EvaluateOptions options; 1.307 + options.setNeedResult(false); 1.308 + JS::RootedValue unused(aCx); 1.309 + return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions, 1.310 + options, &unused, aOffThreadToken); 1.311 +} 1.312 + 1.313 +nsresult 1.314 +nsJSUtils::EvaluateString(JSContext* aCx, 1.315 + JS::SourceBufferHolder& aSrcBuf, 1.316 + JS::Handle<JSObject*> aScopeObject, 1.317 + JS::CompileOptions& aCompileOptions, 1.318 + void **aOffThreadToken) 1.319 +{ 1.320 + EvaluateOptions options; 1.321 + options.setNeedResult(false); 1.322 + JS::RootedValue unused(aCx); 1.323 + return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions, 1.324 + options, &unused, aOffThreadToken); 1.325 +} 1.326 + 1.327 +// 1.328 +// nsDOMJSUtils.h 1.329 +// 1.330 + 1.331 +JSObject* GetDefaultScopeFromJSContext(JSContext *cx) 1.332 +{ 1.333 + // DOM JSContexts don't store their default compartment object on 1.334 + // the cx, so in those cases we need to fetch it via the scx 1.335 + // instead. 1.336 + nsIScriptContext *scx = GetScriptContextFromJSContext(cx); 1.337 + if (scx) { 1.338 + return scx->GetWindowProxy(); 1.339 + } 1.340 + return js::DefaultObjectForContextOrNull(cx); 1.341 +}