1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "jsfriendapi.h"
17 #include "nsIScriptContext.h"
18 #include "nsIScriptGlobalObject.h"
19 #include "nsIXPConnect.h"
20 #include "nsCOMPtr.h"
21 #include "nsIScriptSecurityManager.h"
22 #include "nsPIDOMWindow.h"
23 #include "GeckoProfiler.h"
24 #include "nsJSPrincipals.h"
25 #include "xpcpublic.h"
26 #include "nsContentUtils.h"
27 #include "nsGlobalWindow.h"
28 
29 #include "mozilla/dom/BindingUtils.h"
30 #include "mozilla/dom/Date.h"
31 #include "mozilla/dom/Element.h"
32 #include "mozilla/dom/ScriptSettings.h"
33 
34 using namespace mozilla::dom;
35 
36 bool
GetCallingLocation(JSContext * aContext,nsACString & aFilename,uint32_t * aLineno,uint32_t * aColumn)37 nsJSUtils::GetCallingLocation(JSContext* aContext, nsACString& aFilename,
38                               uint32_t* aLineno, uint32_t* aColumn)
39 {
40   JS::AutoFilename filename;
41   if (!JS::DescribeScriptedCaller(aContext, &filename, aLineno, aColumn)) {
42     return false;
43   }
44 
45   aFilename.Assign(filename.get());
46   return true;
47 }
48 
49 bool
GetCallingLocation(JSContext * aContext,nsAString & aFilename,uint32_t * aLineno,uint32_t * aColumn)50 nsJSUtils::GetCallingLocation(JSContext* aContext, nsAString& aFilename,
51                               uint32_t* aLineno, uint32_t* aColumn)
52 {
53   JS::AutoFilename filename;
54   if (!JS::DescribeScriptedCaller(aContext, &filename, aLineno, aColumn)) {
55     return false;
56   }
57 
58   aFilename.Assign(NS_ConvertUTF8toUTF16(filename.get()));
59   return true;
60 }
61 
62 nsIScriptGlobalObject *
GetStaticScriptGlobal(JSObject * aObj)63 nsJSUtils::GetStaticScriptGlobal(JSObject* aObj)
64 {
65   if (!aObj)
66     return nullptr;
67   return xpc::WindowGlobalOrNull(aObj);
68 }
69 
70 nsIScriptContext *
GetStaticScriptContext(JSObject * aObj)71 nsJSUtils::GetStaticScriptContext(JSObject* aObj)
72 {
73   nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj);
74   if (!nativeGlobal)
75     return nullptr;
76 
77   return nativeGlobal->GetScriptContext();
78 }
79 
80 uint64_t
GetCurrentlyRunningCodeInnerWindowID(JSContext * aContext)81 nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext)
82 {
83   if (!aContext)
84     return 0;
85 
86   nsGlobalWindow* win = xpc::CurrentWindowOrNull(aContext);
87   return win ? win->WindowID() : 0;
88 }
89 
90 nsresult
CompileFunction(AutoJSAPI & jsapi,JS::AutoObjectVector & aScopeChain,JS::CompileOptions & aOptions,const nsACString & aName,uint32_t aArgCount,const char ** aArgArray,const nsAString & aBody,JSObject ** aFunctionObject)91 nsJSUtils::CompileFunction(AutoJSAPI& jsapi,
92                            JS::AutoObjectVector& aScopeChain,
93                            JS::CompileOptions& aOptions,
94                            const nsACString& aName,
95                            uint32_t aArgCount,
96                            const char** aArgArray,
97                            const nsAString& aBody,
98                            JSObject** aFunctionObject)
99 {
100   JSContext* cx = jsapi.cx();
101   MOZ_ASSERT(js::GetEnterCompartmentDepth(cx) > 0);
102   MOZ_ASSERT_IF(aScopeChain.length() != 0,
103                 js::IsObjectInContextCompartment(aScopeChain[0], cx));
104   MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN);
105 
106   // Do the junk Gecko is supposed to do before calling into JSAPI.
107   for (size_t i = 0; i < aScopeChain.length(); ++i) {
108     JS::ExposeObjectToActiveJS(aScopeChain[i]);
109   }
110 
111   // Compile.
112   JS::Rooted<JSFunction*> fun(cx);
113   if (!JS::CompileFunction(cx, aScopeChain, aOptions,
114                            PromiseFlatCString(aName).get(),
115                            aArgCount, aArgArray,
116                            PromiseFlatString(aBody).get(),
117                            aBody.Length(), &fun))
118   {
119     return NS_ERROR_FAILURE;
120   }
121 
122   *aFunctionObject = JS_GetFunctionObject(fun);
123   return NS_OK;
124 }
125 
126 nsresult
EvaluateString(JSContext * aCx,const nsAString & aScript,JS::Handle<JSObject * > aEvaluationGlobal,JS::CompileOptions & aCompileOptions,const EvaluateOptions & aEvaluateOptions,JS::MutableHandle<JS::Value> aRetValue)127 nsJSUtils::EvaluateString(JSContext* aCx,
128                           const nsAString& aScript,
129                           JS::Handle<JSObject*> aEvaluationGlobal,
130                           JS::CompileOptions& aCompileOptions,
131                           const EvaluateOptions& aEvaluateOptions,
132                           JS::MutableHandle<JS::Value> aRetValue)
133 {
134   const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
135   JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
136                                 JS::SourceBufferHolder::NoOwnership);
137   return EvaluateString(aCx, srcBuf, aEvaluationGlobal, aCompileOptions,
138                         aEvaluateOptions, aRetValue, nullptr);
139 }
140 
141 nsresult
EvaluateString(JSContext * aCx,JS::SourceBufferHolder & aSrcBuf,JS::Handle<JSObject * > aEvaluationGlobal,JS::CompileOptions & aCompileOptions,const EvaluateOptions & aEvaluateOptions,JS::MutableHandle<JS::Value> aRetValue,void ** aOffThreadToken)142 nsJSUtils::EvaluateString(JSContext* aCx,
143                           JS::SourceBufferHolder& aSrcBuf,
144                           JS::Handle<JSObject*> aEvaluationGlobal,
145                           JS::CompileOptions& aCompileOptions,
146                           const EvaluateOptions& aEvaluateOptions,
147                           JS::MutableHandle<JS::Value> aRetValue,
148                           void **aOffThreadToken)
149 {
150   PROFILER_LABEL("nsJSUtils", "EvaluateString",
151     js::ProfileEntry::Category::JS);
152 
153   MOZ_ASSERT_IF(aCompileOptions.versionSet,
154                 aCompileOptions.version != JSVERSION_UNKNOWN);
155   MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, !aCompileOptions.noScriptRval);
156   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
157   MOZ_ASSERT(aSrcBuf.get());
158   MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
159              aEvaluationGlobal);
160   MOZ_ASSERT_IF(aOffThreadToken, aCompileOptions.noScriptRval);
161   MOZ_ASSERT(NS_IsMainThread());
162   MOZ_ASSERT(nsContentUtils::IsInMicroTask());
163 
164   // Unfortunately, the JS engine actually compiles scripts with a return value
165   // in a different, less efficient way.  Furthermore, it can't JIT them in many
166   // cases.  So we need to be explicitly told whether the caller cares about the
167   // return value.  Callers can do this by calling the other overload of
168   // EvaluateString() which calls this function with
169   // aCompileOptions.noScriptRval set to true.
170   aRetValue.setUndefined();
171 
172   nsresult rv = NS_OK;
173 
174   NS_ENSURE_TRUE(xpc::Scriptability::Get(aEvaluationGlobal).Allowed(), NS_OK);
175 
176   bool ok = true;
177   // Scope the JSAutoCompartment so that we can later wrap the return value
178   // into the caller's cx.
179   {
180     JSAutoCompartment ac(aCx, aEvaluationGlobal);
181 
182     // Now make sure to wrap the scope chain into the right compartment.
183     JS::AutoObjectVector scopeChain(aCx);
184     if (!scopeChain.reserve(aEvaluateOptions.scopeChain.length())) {
185       return NS_ERROR_OUT_OF_MEMORY;
186     }
187 
188     for (size_t i = 0; i < aEvaluateOptions.scopeChain.length(); ++i) {
189       JS::ExposeObjectToActiveJS(aEvaluateOptions.scopeChain[i]);
190       scopeChain.infallibleAppend(aEvaluateOptions.scopeChain[i]);
191       if (!JS_WrapObject(aCx, scopeChain[i])) {
192         ok = false;
193         break;
194       }
195     }
196 
197     if (ok && aOffThreadToken) {
198       JS::Rooted<JSScript*>
199         script(aCx, JS::FinishOffThreadScript(aCx, *aOffThreadToken));
200       *aOffThreadToken = nullptr; // Mark the token as having been finished.
201       if (script) {
202         ok = JS_ExecuteScript(aCx, scopeChain, script);
203       } else {
204         ok = false;
205       }
206     } else if (ok) {
207       ok = JS::Evaluate(aCx, scopeChain, aCompileOptions, aSrcBuf, aRetValue);
208     }
209 
210     if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) {
211       JS::Rooted<JS::Value> value(aCx, aRetValue);
212       JSString* str = JS::ToString(aCx, value);
213       ok = !!str;
214       aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue());
215     }
216   }
217 
218   if (!ok) {
219     if (JS_IsExceptionPending(aCx)) {
220       rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW;
221     } else {
222       rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE;
223     }
224 
225     if (!aCompileOptions.noScriptRval) {
226       aRetValue.setUndefined();
227     }
228   }
229 
230   // Wrap the return value into whatever compartment aCx was in.
231   if (ok && !aCompileOptions.noScriptRval) {
232     if (!JS_WrapValue(aCx, aRetValue)) {
233       return NS_ERROR_OUT_OF_MEMORY;
234     }
235   }
236   return rv;
237 }
238 
239 nsresult
EvaluateString(JSContext * aCx,JS::SourceBufferHolder & aSrcBuf,JS::Handle<JSObject * > aEvaluationGlobal,JS::CompileOptions & aCompileOptions,const EvaluateOptions & aEvaluateOptions,JS::MutableHandle<JS::Value> aRetValue)240 nsJSUtils::EvaluateString(JSContext* aCx,
241                           JS::SourceBufferHolder& aSrcBuf,
242                           JS::Handle<JSObject*> aEvaluationGlobal,
243                           JS::CompileOptions& aCompileOptions,
244                           const EvaluateOptions& aEvaluateOptions,
245                           JS::MutableHandle<JS::Value> aRetValue)
246 {
247   return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions,
248                         aEvaluateOptions, aRetValue, nullptr);
249 }
250 
251 nsresult
EvaluateString(JSContext * aCx,const nsAString & aScript,JS::Handle<JSObject * > aEvaluationGlobal,JS::CompileOptions & aCompileOptions)252 nsJSUtils::EvaluateString(JSContext* aCx,
253                           const nsAString& aScript,
254                           JS::Handle<JSObject*> aEvaluationGlobal,
255                           JS::CompileOptions& aCompileOptions)
256 {
257   EvaluateOptions options(aCx);
258   aCompileOptions.setNoScriptRval(true);
259   JS::RootedValue unused(aCx);
260   return EvaluateString(aCx, aScript, aEvaluationGlobal, aCompileOptions,
261                         options, &unused);
262 }
263 
264 nsresult
EvaluateString(JSContext * aCx,JS::SourceBufferHolder & aSrcBuf,JS::Handle<JSObject * > aEvaluationGlobal,JS::CompileOptions & aCompileOptions,void ** aOffThreadToken)265 nsJSUtils::EvaluateString(JSContext* aCx,
266                           JS::SourceBufferHolder& aSrcBuf,
267                           JS::Handle<JSObject*> aEvaluationGlobal,
268                           JS::CompileOptions& aCompileOptions,
269                           void **aOffThreadToken)
270 {
271   EvaluateOptions options(aCx);
272   aCompileOptions.setNoScriptRval(true);
273   JS::RootedValue unused(aCx);
274   return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions,
275                         options, &unused, aOffThreadToken);
276 }
277 
278 nsresult
CompileModule(JSContext * aCx,JS::SourceBufferHolder & aSrcBuf,JS::Handle<JSObject * > aEvaluationGlobal,JS::CompileOptions & aCompileOptions,JS::MutableHandle<JSObject * > aModule)279 nsJSUtils::CompileModule(JSContext* aCx,
280                        JS::SourceBufferHolder& aSrcBuf,
281                        JS::Handle<JSObject*> aEvaluationGlobal,
282                        JS::CompileOptions &aCompileOptions,
283                        JS::MutableHandle<JSObject*> aModule)
284 {
285   PROFILER_LABEL("nsJSUtils", "CompileModule",
286     js::ProfileEntry::Category::JS);
287 
288   MOZ_ASSERT_IF(aCompileOptions.versionSet,
289                 aCompileOptions.version != JSVERSION_UNKNOWN);
290   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
291   MOZ_ASSERT(aSrcBuf.get());
292   MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
293              aEvaluationGlobal);
294   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal);
295   MOZ_ASSERT(NS_IsMainThread());
296   MOZ_ASSERT(nsContentUtils::IsInMicroTask());
297 
298   NS_ENSURE_TRUE(xpc::Scriptability::Get(aEvaluationGlobal).Allowed(), NS_OK);
299 
300   if (!JS::CompileModule(aCx, aCompileOptions, aSrcBuf, aModule)) {
301     return NS_ERROR_FAILURE;
302   }
303 
304   return NS_OK;
305 }
306 
307 nsresult
ModuleDeclarationInstantiation(JSContext * aCx,JS::Handle<JSObject * > aModule)308 nsJSUtils::ModuleDeclarationInstantiation(JSContext* aCx, JS::Handle<JSObject*> aModule)
309 {
310   PROFILER_LABEL("nsJSUtils", "ModuleDeclarationInstantiation",
311     js::ProfileEntry::Category::JS);
312 
313   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
314   MOZ_ASSERT(NS_IsMainThread());
315 
316   NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
317 
318   if (!JS::ModuleDeclarationInstantiation(aCx, aModule)) {
319     return NS_ERROR_FAILURE;
320   }
321 
322   return NS_OK;
323 }
324 
325 nsresult
ModuleEvaluation(JSContext * aCx,JS::Handle<JSObject * > aModule)326 nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule)
327 {
328   PROFILER_LABEL("nsJSUtils", "ModuleEvaluation",
329     js::ProfileEntry::Category::JS);
330 
331   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
332   MOZ_ASSERT(NS_IsMainThread());
333   MOZ_ASSERT(nsContentUtils::IsInMicroTask());
334 
335   NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
336 
337   if (!JS::ModuleEvaluation(aCx, aModule)) {
338     return NS_ERROR_FAILURE;
339   }
340 
341   return NS_OK;
342 }
343 
344 /* static */
345 bool
GetScopeChainForElement(JSContext * aCx,mozilla::dom::Element * aElement,JS::AutoObjectVector & aScopeChain)346 nsJSUtils::GetScopeChainForElement(JSContext* aCx,
347                                    mozilla::dom::Element* aElement,
348                                    JS::AutoObjectVector& aScopeChain)
349 {
350   for (nsINode* cur = aElement; cur; cur = cur->GetScopeChainParent()) {
351     JS::RootedValue val(aCx);
352     if (!GetOrCreateDOMReflector(aCx, cur, &val)) {
353       return false;
354     }
355 
356     if (!aScopeChain.append(&val.toObject())) {
357       return false;
358     }
359   }
360 
361   return true;
362 }
363 
364 /* static */
365 void
ResetTimeZone()366 nsJSUtils::ResetTimeZone()
367 {
368   JS::ResetTimeZone();
369 }
370 
371 //
372 // nsDOMJSUtils.h
373 //
374 
init(const JS::Value & v)375 bool nsAutoJSString::init(const JS::Value &v)
376 {
377   // Note: it's okay to use danger::GetJSContext here instead of AutoJSAPI,
378   // because the init() call below is careful not to run script (for instance,
379   // it only calls JS::ToString for non-object values).
380   JSContext* cx = danger::GetJSContext();
381   if (!init(cx, v)) {
382     JS_ClearPendingException(cx);
383     return false;
384   }
385 
386   return true;
387 }
388 
389