Bumping manifests a=b2g-bump
[gecko.git] / dom / base / nsJSUtils.cpp
blob96c4e25cce48d8adaf6191d6d93aed1ad0ff0390
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /**
8 * This is not a generated file. It contains common utility functions
9 * invoked from the JavaScript code generated from IDL interfaces.
10 * The goal of the utility functions is to cut down on the size of
11 * the generated code itself.
14 #include "nsJSUtils.h"
15 #include "jsapi.h"
16 #include "js/OldDebugAPI.h"
17 #include "jsfriendapi.h"
18 #include "nsIScriptContext.h"
19 #include "nsIScriptGlobalObject.h"
20 #include "nsIXPConnect.h"
21 #include "nsCOMPtr.h"
22 #include "nsIScriptSecurityManager.h"
23 #include "nsPIDOMWindow.h"
24 #include "GeckoProfiler.h"
25 #include "nsDOMJSUtils.h" // for GetScriptContextFromJSContext
26 #include "nsJSPrincipals.h"
27 #include "xpcpublic.h"
28 #include "nsContentUtils.h"
29 #include "nsGlobalWindow.h"
31 bool
32 nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename,
33 uint32_t* aLineno)
35 JS::AutoFilename filename;
36 unsigned lineno = 0;
38 if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) {
39 return false;
42 *aFilename = filename.get();
43 *aLineno = lineno;
45 return true;
48 nsIScriptGlobalObject *
49 nsJSUtils::GetStaticScriptGlobal(JSObject* aObj)
51 if (!aObj)
52 return nullptr;
53 return xpc::WindowGlobalOrNull(aObj);
56 nsIScriptContext *
57 nsJSUtils::GetStaticScriptContext(JSObject* aObj)
59 nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj);
60 if (!nativeGlobal)
61 return nullptr;
63 return nativeGlobal->GetScriptContext();
66 uint64_t
67 nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext)
69 if (!aContext)
70 return 0;
72 uint64_t innerWindowID = 0;
74 JSObject *jsGlobal = JS::CurrentGlobalOrNull(aContext);
75 if (jsGlobal) {
76 nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(jsGlobal);
77 if (scriptGlobal) {
78 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(scriptGlobal);
79 if (win)
80 innerWindowID = win->WindowID();
84 return innerWindowID;
87 void
88 nsJSUtils::ReportPendingException(JSContext *aContext)
90 if (JS_IsExceptionPending(aContext)) {
91 bool saved = JS_SaveFrameChain(aContext);
93 // JS_SaveFrameChain set the compartment of aContext to null, so we need
94 // to enter a compartment. The question is, which one? We don't want to
95 // enter the original compartment of aContext (or the compartment of the
96 // current exception on aContext, for that matter) because when we
97 // JS_ReportPendingException the JS engine can try to duck-type the
98 // exception and produce a JSErrorReport. It will then pass that
99 // JSErrorReport to the error reporter on aContext, which might expose
100 // information from it to script via onerror handlers. So it's very
101 // important that the duck typing happen in the same compartment as the
102 // onerror handler. In practice, that's the compartment of the window (or
103 // otherwise default global) of aContext, so use that here.
104 nsIScriptContext* scx = GetScriptContextFromJSContext(aContext);
105 JS::Rooted<JSObject*> scope(aContext);
106 scope = scx ? scx->GetWindowProxy() : nullptr;
107 if (!scope) {
108 // The SafeJSContext has no default object associated with it.
109 MOZ_ASSERT(NS_IsMainThread());
110 MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext());
111 scope = xpc::UnprivilegedJunkScope(); // Usage approved by bholley
113 JSAutoCompartment ac(aContext, scope);
114 JS_ReportPendingException(aContext);
116 if (saved) {
117 JS_RestoreFrameChain(aContext);
122 nsresult
123 nsJSUtils::CompileFunction(JSContext* aCx,
124 JS::Handle<JSObject*> aTarget,
125 JS::CompileOptions& aOptions,
126 const nsACString& aName,
127 uint32_t aArgCount,
128 const char** aArgArray,
129 const nsAString& aBody,
130 JSObject** aFunctionObject)
132 MOZ_ASSERT(js::GetEnterCompartmentDepth(aCx) > 0);
133 MOZ_ASSERT_IF(aTarget, js::IsObjectInContextCompartment(aTarget, aCx));
134 MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN);
135 mozilla::DebugOnly<nsIScriptContext*> ctx = GetScriptContextFromJSContext(aCx);
136 MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized());
138 // Do the junk Gecko is supposed to do before calling into JSAPI.
139 if (aTarget) {
140 JS::ExposeObjectToActiveJS(aTarget);
143 // Compile.
144 JS::Rooted<JSFunction*> fun(aCx);
145 if (!JS::CompileFunction(aCx, aTarget, aOptions,
146 PromiseFlatCString(aName).get(),
147 aArgCount, aArgArray,
148 PromiseFlatString(aBody).get(),
149 aBody.Length(), &fun))
151 ReportPendingException(aCx);
152 return NS_ERROR_FAILURE;
155 *aFunctionObject = JS_GetFunctionObject(fun);
156 return NS_OK;
159 nsresult
160 nsJSUtils::EvaluateString(JSContext* aCx,
161 const nsAString& aScript,
162 JS::Handle<JSObject*> aScopeObject,
163 JS::CompileOptions& aCompileOptions,
164 const EvaluateOptions& aEvaluateOptions,
165 JS::MutableHandle<JS::Value> aRetValue,
166 void **aOffThreadToken)
168 const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
169 JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
170 JS::SourceBufferHolder::NoOwnership);
171 return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions,
172 aEvaluateOptions, aRetValue, aOffThreadToken);
175 nsresult
176 nsJSUtils::EvaluateString(JSContext* aCx,
177 JS::SourceBufferHolder& aSrcBuf,
178 JS::Handle<JSObject*> aScopeObject,
179 JS::CompileOptions& aCompileOptions,
180 const EvaluateOptions& aEvaluateOptions,
181 JS::MutableHandle<JS::Value> aRetValue,
182 void **aOffThreadToken)
184 PROFILER_LABEL("nsJSUtils", "EvaluateString",
185 js::ProfileEntry::Category::JS);
187 MOZ_ASSERT_IF(aCompileOptions.versionSet,
188 aCompileOptions.version != JSVERSION_UNKNOWN);
189 MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult);
190 MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult);
191 MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
192 MOZ_ASSERT(aSrcBuf.get());
194 // Unfortunately, the JS engine actually compiles scripts with a return value
195 // in a different, less efficient way. Furthermore, it can't JIT them in many
196 // cases. So we need to be explicitly told whether the caller cares about the
197 // return value. Callers can do this by calling the other overload of
198 // EvaluateString() which calls this function with aEvaluateOptions.needResult
199 // set to false.
200 aRetValue.setUndefined();
202 JS::ExposeObjectToActiveJS(aScopeObject);
203 nsAutoMicroTask mt;
204 nsresult rv = NS_OK;
206 bool ok = false;
207 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
208 NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK);
210 mozilla::Maybe<AutoDontReportUncaught> dontReport;
211 if (!aEvaluateOptions.reportUncaught) {
212 // We need to prevent AutoLastFrameCheck from reporting and clearing
213 // any pending exceptions.
214 dontReport.emplace(aCx);
217 // Scope the JSAutoCompartment so that we can later wrap the return value
218 // into the caller's cx.
220 JSAutoCompartment ac(aCx, aScopeObject);
222 JS::Rooted<JSObject*> rootedScope(aCx, aScopeObject);
223 if (aOffThreadToken) {
224 JS::Rooted<JSScript*>
225 script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken));
226 *aOffThreadToken = nullptr; // Mark the token as having been finished.
227 if (script) {
228 if (aEvaluateOptions.needResult) {
229 ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue);
230 } else {
231 ok = JS_ExecuteScript(aCx, rootedScope, script);
233 } else {
234 ok = false;
236 } else {
237 if (aEvaluateOptions.needResult) {
238 ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
239 aSrcBuf, aRetValue);
240 } else {
241 ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
242 aSrcBuf);
246 if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) {
247 JS::Rooted<JS::Value> value(aCx, aRetValue);
248 JSString* str = JS::ToString(aCx, value);
249 ok = !!str;
250 aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue());
254 if (!ok) {
255 if (aEvaluateOptions.reportUncaught) {
256 ReportPendingException(aCx);
257 if (aEvaluateOptions.needResult) {
258 aRetValue.setUndefined();
260 } else {
261 rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE
262 : NS_ERROR_OUT_OF_MEMORY;
263 JS::Rooted<JS::Value> exn(aCx);
264 JS_GetPendingException(aCx, &exn);
265 if (aEvaluateOptions.needResult) {
266 aRetValue.set(exn);
268 JS_ClearPendingException(aCx);
272 // Wrap the return value into whatever compartment aCx was in.
273 if (aEvaluateOptions.needResult) {
274 JS::Rooted<JS::Value> v(aCx, aRetValue);
275 if (!JS_WrapValue(aCx, &v)) {
276 return NS_ERROR_OUT_OF_MEMORY;
278 aRetValue.set(v);
280 return rv;
283 nsresult
284 nsJSUtils::EvaluateString(JSContext* aCx,
285 const nsAString& aScript,
286 JS::Handle<JSObject*> aScopeObject,
287 JS::CompileOptions& aCompileOptions,
288 void **aOffThreadToken)
290 EvaluateOptions options;
291 options.setNeedResult(false);
292 JS::RootedValue unused(aCx);
293 return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions,
294 options, &unused, aOffThreadToken);
297 nsresult
298 nsJSUtils::EvaluateString(JSContext* aCx,
299 JS::SourceBufferHolder& aSrcBuf,
300 JS::Handle<JSObject*> aScopeObject,
301 JS::CompileOptions& aCompileOptions,
302 void **aOffThreadToken)
304 EvaluateOptions options;
305 options.setNeedResult(false);
306 JS::RootedValue unused(aCx);
307 return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions,
308 options, &unused, aOffThreadToken);
312 // nsDOMJSUtils.h
315 JSObject* GetDefaultScopeFromJSContext(JSContext *cx)
317 // DOM JSContexts don't store their default compartment object on
318 // the cx, so in those cases we need to fetch it via the scx
319 // instead.
320 nsIScriptContext *scx = GetScriptContextFromJSContext(cx);
321 return scx ? scx->GetWindowProxy() : nullptr;