|
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/. */ |
|
6 |
|
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. |
|
12 */ |
|
13 |
|
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" |
|
30 |
|
31 bool |
|
32 nsJSUtils::GetCallingLocation(JSContext* aContext, const char* *aFilename, |
|
33 uint32_t* aLineno) |
|
34 { |
|
35 JS::AutoFilename filename; |
|
36 unsigned lineno = 0; |
|
37 |
|
38 if (!JS::DescribeScriptedCaller(aContext, &filename, &lineno)) { |
|
39 return false; |
|
40 } |
|
41 |
|
42 *aFilename = filename.get(); |
|
43 *aLineno = lineno; |
|
44 |
|
45 return true; |
|
46 } |
|
47 |
|
48 nsIScriptGlobalObject * |
|
49 nsJSUtils::GetStaticScriptGlobal(JSObject* aObj) |
|
50 { |
|
51 if (!aObj) |
|
52 return nullptr; |
|
53 return xpc::WindowGlobalOrNull(aObj); |
|
54 } |
|
55 |
|
56 nsIScriptContext * |
|
57 nsJSUtils::GetStaticScriptContext(JSObject* aObj) |
|
58 { |
|
59 nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj); |
|
60 if (!nativeGlobal) |
|
61 return nullptr; |
|
62 |
|
63 return nativeGlobal->GetScriptContext(); |
|
64 } |
|
65 |
|
66 nsIScriptGlobalObject * |
|
67 nsJSUtils::GetDynamicScriptGlobal(JSContext* aContext) |
|
68 { |
|
69 nsIScriptContext *scriptCX = GetDynamicScriptContext(aContext); |
|
70 if (!scriptCX) |
|
71 return nullptr; |
|
72 return scriptCX->GetGlobalObject(); |
|
73 } |
|
74 |
|
75 nsIScriptContext * |
|
76 nsJSUtils::GetDynamicScriptContext(JSContext *aContext) |
|
77 { |
|
78 return GetScriptContextFromJSContext(aContext); |
|
79 } |
|
80 |
|
81 uint64_t |
|
82 nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext) |
|
83 { |
|
84 if (!aContext) |
|
85 return 0; |
|
86 |
|
87 uint64_t innerWindowID = 0; |
|
88 |
|
89 JSObject *jsGlobal = JS::CurrentGlobalOrNull(aContext); |
|
90 if (jsGlobal) { |
|
91 nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(jsGlobal); |
|
92 if (scriptGlobal) { |
|
93 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(scriptGlobal); |
|
94 if (win) |
|
95 innerWindowID = win->WindowID(); |
|
96 } |
|
97 } |
|
98 |
|
99 return innerWindowID; |
|
100 } |
|
101 |
|
102 void |
|
103 nsJSUtils::ReportPendingException(JSContext *aContext) |
|
104 { |
|
105 if (JS_IsExceptionPending(aContext)) { |
|
106 bool saved = JS_SaveFrameChain(aContext); |
|
107 { |
|
108 // JS_SaveFrameChain set the compartment of aContext to null, so we need |
|
109 // to enter a compartment. The question is, which one? We don't want to |
|
110 // enter the original compartment of aContext (or the compartment of the |
|
111 // current exception on aContext, for that matter) because when we |
|
112 // JS_ReportPendingException the JS engine can try to duck-type the |
|
113 // exception and produce a JSErrorReport. It will then pass that |
|
114 // JSErrorReport to the error reporter on aContext, which might expose |
|
115 // information from it to script via onerror handlers. So it's very |
|
116 // important that the duck typing happen in the same compartment as the |
|
117 // onerror handler. In practice, that's the compartment of the window (or |
|
118 // otherwise default global) of aContext, so use that here. |
|
119 nsIScriptContext* scx = GetScriptContextFromJSContext(aContext); |
|
120 JS::Rooted<JSObject*> scope(aContext); |
|
121 scope = scx ? scx->GetWindowProxy() |
|
122 : js::DefaultObjectForContextOrNull(aContext); |
|
123 if (!scope) { |
|
124 // The SafeJSContext has no default object associated with it. |
|
125 MOZ_ASSERT(NS_IsMainThread()); |
|
126 MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext()); |
|
127 scope = xpc::GetSafeJSContextGlobal(); |
|
128 } |
|
129 JSAutoCompartment ac(aContext, scope); |
|
130 JS_ReportPendingException(aContext); |
|
131 } |
|
132 if (saved) { |
|
133 JS_RestoreFrameChain(aContext); |
|
134 } |
|
135 } |
|
136 } |
|
137 |
|
138 nsresult |
|
139 nsJSUtils::CompileFunction(JSContext* aCx, |
|
140 JS::Handle<JSObject*> aTarget, |
|
141 JS::CompileOptions& aOptions, |
|
142 const nsACString& aName, |
|
143 uint32_t aArgCount, |
|
144 const char** aArgArray, |
|
145 const nsAString& aBody, |
|
146 JSObject** aFunctionObject) |
|
147 { |
|
148 MOZ_ASSERT(js::GetEnterCompartmentDepth(aCx) > 0); |
|
149 MOZ_ASSERT_IF(aTarget, js::IsObjectInContextCompartment(aTarget, aCx)); |
|
150 MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN); |
|
151 mozilla::DebugOnly<nsIScriptContext*> ctx = GetScriptContextFromJSContext(aCx); |
|
152 MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized()); |
|
153 |
|
154 // Do the junk Gecko is supposed to do before calling into JSAPI. |
|
155 if (aTarget) { |
|
156 JS::ExposeObjectToActiveJS(aTarget); |
|
157 } |
|
158 |
|
159 // Compile. |
|
160 JSFunction* fun = JS::CompileFunction(aCx, aTarget, aOptions, |
|
161 PromiseFlatCString(aName).get(), |
|
162 aArgCount, aArgArray, |
|
163 PromiseFlatString(aBody).get(), |
|
164 aBody.Length()); |
|
165 if (!fun) { |
|
166 ReportPendingException(aCx); |
|
167 return NS_ERROR_FAILURE; |
|
168 } |
|
169 |
|
170 *aFunctionObject = JS_GetFunctionObject(fun); |
|
171 return NS_OK; |
|
172 } |
|
173 |
|
174 nsresult |
|
175 nsJSUtils::EvaluateString(JSContext* aCx, |
|
176 const nsAString& aScript, |
|
177 JS::Handle<JSObject*> aScopeObject, |
|
178 JS::CompileOptions& aCompileOptions, |
|
179 const EvaluateOptions& aEvaluateOptions, |
|
180 JS::MutableHandle<JS::Value> aRetValue, |
|
181 void **aOffThreadToken) |
|
182 { |
|
183 const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); |
|
184 JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), |
|
185 JS::SourceBufferHolder::NoOwnership); |
|
186 return EvaluateString(aCx, srcBuf, aScopeObject, aCompileOptions, |
|
187 aEvaluateOptions, aRetValue, aOffThreadToken); |
|
188 } |
|
189 |
|
190 nsresult |
|
191 nsJSUtils::EvaluateString(JSContext* aCx, |
|
192 JS::SourceBufferHolder& aSrcBuf, |
|
193 JS::Handle<JSObject*> aScopeObject, |
|
194 JS::CompileOptions& aCompileOptions, |
|
195 const EvaluateOptions& aEvaluateOptions, |
|
196 JS::MutableHandle<JS::Value> aRetValue, |
|
197 void **aOffThreadToken) |
|
198 { |
|
199 PROFILER_LABEL("JS", "EvaluateString"); |
|
200 MOZ_ASSERT_IF(aCompileOptions.versionSet, |
|
201 aCompileOptions.version != JSVERSION_UNKNOWN); |
|
202 MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult); |
|
203 MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult); |
|
204 MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); |
|
205 MOZ_ASSERT(aSrcBuf.get()); |
|
206 |
|
207 // Unfortunately, the JS engine actually compiles scripts with a return value |
|
208 // in a different, less efficient way. Furthermore, it can't JIT them in many |
|
209 // cases. So we need to be explicitly told whether the caller cares about the |
|
210 // return value. Callers can do this by calling the other overload of |
|
211 // EvaluateString() which calls this function with aEvaluateOptions.needResult |
|
212 // set to false. |
|
213 aRetValue.setUndefined(); |
|
214 |
|
215 JS::ExposeObjectToActiveJS(aScopeObject); |
|
216 nsAutoMicroTask mt; |
|
217 nsresult rv = NS_OK; |
|
218 |
|
219 bool ok = false; |
|
220 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); |
|
221 NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK); |
|
222 |
|
223 mozilla::Maybe<AutoDontReportUncaught> dontReport; |
|
224 if (!aEvaluateOptions.reportUncaught) { |
|
225 // We need to prevent AutoLastFrameCheck from reporting and clearing |
|
226 // any pending exceptions. |
|
227 dontReport.construct(aCx); |
|
228 } |
|
229 |
|
230 // Scope the JSAutoCompartment so that we can later wrap the return value |
|
231 // into the caller's cx. |
|
232 { |
|
233 JSAutoCompartment ac(aCx, aScopeObject); |
|
234 |
|
235 JS::Rooted<JSObject*> rootedScope(aCx, aScopeObject); |
|
236 if (aOffThreadToken) { |
|
237 JS::Rooted<JSScript*> |
|
238 script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken)); |
|
239 *aOffThreadToken = nullptr; // Mark the token as having been finished. |
|
240 if (script) { |
|
241 if (aEvaluateOptions.needResult) { |
|
242 ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue); |
|
243 } else { |
|
244 ok = JS_ExecuteScript(aCx, rootedScope, script); |
|
245 } |
|
246 } else { |
|
247 ok = false; |
|
248 } |
|
249 } else { |
|
250 if (aEvaluateOptions.needResult) { |
|
251 ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, |
|
252 aSrcBuf, aRetValue); |
|
253 } else { |
|
254 ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, |
|
255 aSrcBuf); |
|
256 } |
|
257 } |
|
258 |
|
259 if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) { |
|
260 JS::Rooted<JS::Value> value(aCx, aRetValue); |
|
261 JSString* str = JS::ToString(aCx, value); |
|
262 ok = !!str; |
|
263 aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue()); |
|
264 } |
|
265 } |
|
266 |
|
267 if (!ok) { |
|
268 if (aEvaluateOptions.reportUncaught) { |
|
269 ReportPendingException(aCx); |
|
270 if (aEvaluateOptions.needResult) { |
|
271 aRetValue.setUndefined(); |
|
272 } |
|
273 } else { |
|
274 rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE |
|
275 : NS_ERROR_OUT_OF_MEMORY; |
|
276 JS::Rooted<JS::Value> exn(aCx); |
|
277 JS_GetPendingException(aCx, &exn); |
|
278 if (aEvaluateOptions.needResult) { |
|
279 aRetValue.set(exn); |
|
280 } |
|
281 JS_ClearPendingException(aCx); |
|
282 } |
|
283 } |
|
284 |
|
285 // Wrap the return value into whatever compartment aCx was in. |
|
286 if (aEvaluateOptions.needResult) { |
|
287 JS::Rooted<JS::Value> v(aCx, aRetValue); |
|
288 if (!JS_WrapValue(aCx, &v)) { |
|
289 return NS_ERROR_OUT_OF_MEMORY; |
|
290 } |
|
291 aRetValue.set(v); |
|
292 } |
|
293 return rv; |
|
294 } |
|
295 |
|
296 nsresult |
|
297 nsJSUtils::EvaluateString(JSContext* aCx, |
|
298 const nsAString& aScript, |
|
299 JS::Handle<JSObject*> aScopeObject, |
|
300 JS::CompileOptions& aCompileOptions, |
|
301 void **aOffThreadToken) |
|
302 { |
|
303 EvaluateOptions options; |
|
304 options.setNeedResult(false); |
|
305 JS::RootedValue unused(aCx); |
|
306 return EvaluateString(aCx, aScript, aScopeObject, aCompileOptions, |
|
307 options, &unused, aOffThreadToken); |
|
308 } |
|
309 |
|
310 nsresult |
|
311 nsJSUtils::EvaluateString(JSContext* aCx, |
|
312 JS::SourceBufferHolder& aSrcBuf, |
|
313 JS::Handle<JSObject*> aScopeObject, |
|
314 JS::CompileOptions& aCompileOptions, |
|
315 void **aOffThreadToken) |
|
316 { |
|
317 EvaluateOptions options; |
|
318 options.setNeedResult(false); |
|
319 JS::RootedValue unused(aCx); |
|
320 return EvaluateString(aCx, aSrcBuf, aScopeObject, aCompileOptions, |
|
321 options, &unused, aOffThreadToken); |
|
322 } |
|
323 |
|
324 // |
|
325 // nsDOMJSUtils.h |
|
326 // |
|
327 |
|
328 JSObject* GetDefaultScopeFromJSContext(JSContext *cx) |
|
329 { |
|
330 // DOM JSContexts don't store their default compartment object on |
|
331 // the cx, so in those cases we need to fetch it via the scx |
|
332 // instead. |
|
333 nsIScriptContext *scx = GetScriptContextFromJSContext(cx); |
|
334 if (scx) { |
|
335 return scx->GetWindowProxy(); |
|
336 } |
|
337 return js::DefaultObjectForContextOrNull(cx); |
|
338 } |