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 #ifndef nsJSUtils_h__
8 #define nsJSUtils_h__
9 
10 /**
11  * This is not a generated file. It contains common utility functions
12  * invoked from the JavaScript code generated from IDL interfaces.
13  * The goal of the utility functions is to cut down on the size of
14  * the generated code itself.
15  */
16 
17 #include "mozilla/Assertions.h"
18 #include "mozilla/StaticPrefs_dom.h"
19 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
20 
21 #include "GeckoProfiler.h"
22 #include "jsapi.h"
23 #include "jsfriendapi.h"
24 #include "js/Conversions.h"
25 #include "js/SourceText.h"
26 #include "js/StableStringChars.h"
27 #include "nsString.h"
28 #include "xpcpublic.h"
29 
30 class nsIScriptContext;
31 class nsIScriptElement;
32 class nsIScriptGlobalObject;
33 class nsXBLPrototypeBinding;
34 
35 namespace mozilla {
36 namespace dom {
37 class AutoJSAPI;
38 class Element;
39 }  // namespace dom
40 }  // namespace mozilla
41 
42 class nsJSUtils {
43  public:
44   static bool GetCallingLocation(JSContext* aContext, nsACString& aFilename,
45                                  uint32_t* aLineno = nullptr,
46                                  uint32_t* aColumn = nullptr);
47   static bool GetCallingLocation(JSContext* aContext, nsAString& aFilename,
48                                  uint32_t* aLineno = nullptr,
49                                  uint32_t* aColumn = nullptr);
50 
51   /**
52    * Retrieve the inner window ID based on the given JSContext.
53    *
54    * @param JSContext aContext
55    *        The JSContext from which you want to find the inner window ID.
56    *
57    * @returns uint64_t the inner window ID.
58    */
59   static uint64_t GetCurrentlyRunningCodeInnerWindowID(JSContext* aContext);
60 
61   static nsresult CompileFunction(mozilla::dom::AutoJSAPI& jsapi,
62                                   JS::HandleVector<JSObject*> aScopeChain,
63                                   JS::CompileOptions& aOptions,
64                                   const nsACString& aName, uint32_t aArgCount,
65                                   const char** aArgArray,
66                                   const nsAString& aBody,
67                                   JSObject** aFunctionObject);
68 
69   // ExecutionContext is used to switch compartment.
70   class MOZ_STACK_CLASS ExecutionContext {
71 #ifdef MOZ_GECKO_PROFILER
72     // Register stack annotations for the Gecko profiler.
73     mozilla::AutoProfilerLabel mAutoProfilerLabel;
74 #endif
75 
76     JSContext* mCx;
77 
78     // Handles switching to our global's realm.
79     JSAutoRealm mRealm;
80 
81     // Set to a valid handle if a return value is expected.
82     JS::Rooted<JS::Value> mRetValue;
83 
84     // Scope chain in which the execution takes place.
85     JS::RootedVector<JSObject*> mScopeChain;
86 
87     // The compiled script.
88     JS::Rooted<JSScript*> mScript;
89 
90     // returned value forwarded when we have to interupt the execution eagerly
91     // with mSkip.
92     nsresult mRv;
93 
94     // Used to skip upcoming phases in case of a failure.  In such case the
95     // result is carried by mRv.
96     bool mSkip;
97 
98     // Should the result be serialized before being returned.
99     bool mCoerceToString;
100 
101     // Encode the bytecode before it is being executed.
102     bool mEncodeBytecode;
103 
104 #ifdef DEBUG
105     // Should we set the return value.
106     bool mWantsReturnValue;
107 
108     bool mExpectScopeChain;
109 
110     bool mScriptUsed;
111 #endif
112 
113    private:
114     // Compile a script contained in a SourceText.
115     template <typename Unit>
116     nsresult InternalCompile(JS::CompileOptions& aCompileOptions,
117                              JS::SourceText<Unit>& aSrcBuf);
118 
119    public:
120     // Enter compartment in which the code would be executed.  The JSContext
121     // must come from an AutoEntryScript.
122     ExecutionContext(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
123 
124     ExecutionContext(const ExecutionContext&) = delete;
125     ExecutionContext(ExecutionContext&&) = delete;
126 
~ExecutionContext()127     ~ExecutionContext() {
128       // This flag is reset when the returned value is extracted.
129       MOZ_ASSERT_IF(!mSkip, !mWantsReturnValue);
130 
131       // If encoding was started we expect the script to have been
132       // used when ending the encoding.
133       MOZ_ASSERT_IF(mEncodeBytecode && mScript && mRv == NS_OK, mScriptUsed);
134     }
135 
136     // The returned value would be converted to a string if the
137     // |aCoerceToString| is flag set.
SetCoerceToString(bool aCoerceToString)138     ExecutionContext& SetCoerceToString(bool aCoerceToString) {
139       mCoerceToString = aCoerceToString;
140       return *this;
141     }
142 
143     // When set, this flag records and encodes the bytecode as soon as it is
144     // being compiled, and before it is being executed. The bytecode can then be
145     // requested by using |JS::FinishIncrementalEncoding| with the mutable
146     // handle |aScript| argument of |CompileAndExec| or |JoinAndExec|.
SetEncodeBytecode(bool aEncodeBytecode)147     ExecutionContext& SetEncodeBytecode(bool aEncodeBytecode) {
148       mEncodeBytecode = aEncodeBytecode;
149       return *this;
150     }
151 
152     // Set the scope chain in which the code should be executed.
153     void SetScopeChain(JS::HandleVector<JSObject*> aScopeChain);
154 
155     // After getting a notification that an off-thread compilation terminated,
156     // this function will take the result of the parser and move it to the main
157     // thread.
158     MOZ_MUST_USE nsresult JoinCompile(JS::OffThreadToken** aOffThreadToken);
159 
160     // Compile a script contained in a SourceText.
161     nsresult Compile(JS::CompileOptions& aCompileOptions,
162                      JS::SourceText<char16_t>& aSrcBuf);
163     nsresult Compile(JS::CompileOptions& aCompileOptions,
164                      JS::SourceText<mozilla::Utf8Unit>& aSrcBuf);
165 
166     // Compile a script contained in a string.
167     nsresult Compile(JS::CompileOptions& aCompileOptions,
168                      const nsAString& aScript);
169 
170     // Decode a script contained in a buffer.
171     nsresult Decode(JS::CompileOptions& aCompileOptions,
172                     mozilla::Vector<uint8_t>& aBytecodeBuf,
173                     size_t aBytecodeIndex);
174 
175     // After getting a notification that an off-thread decoding terminated, this
176     // function will get the result of the decoder and move it to the main
177     // thread.
178     nsresult JoinDecode(JS::OffThreadToken** aOffThreadToken);
179 
180     nsresult JoinDecodeBinAST(JS::OffThreadToken** aOffThreadToken);
181 
182     // Decode a BinAST encoded script contained in a buffer.
183     nsresult DecodeBinAST(JS::CompileOptions& aCompileOptions,
184                           const uint8_t* aBuf, size_t aLength);
185 
186     // Get a successfully compiled script.
187     JSScript* GetScript();
188 
189     // Get the compiled script if present, or nullptr.
190     JSScript* MaybeGetScript();
191 
192     // Execute the compiled script and ignore the return value.
193     MOZ_MUST_USE nsresult ExecScript();
194 
195     // Execute the compiled script a get the return value.
196     //
197     // Copy the returned value into the mutable handle argument. In case of a
198     // evaluation failure either during the execution or the conversion of the
199     // result to a string, the nsresult is be set to the corresponding result
200     // code and the mutable handle argument remains unchanged.
201     //
202     // The value returned in the mutable handle argument is part of the
203     // compartment given as argument to the ExecutionContext constructor. If the
204     // caller is in a different compartment, then the out-param value should be
205     // wrapped by calling |JS_WrapValue|.
206     MOZ_MUST_USE nsresult ExecScript(JS::MutableHandle<JS::Value> aRetValue);
207   };
208 
BinASTEncodingEnabled()209   static bool BinASTEncodingEnabled() {
210 #ifdef JS_BUILD_BINAST
211     return mozilla::StaticPrefs::dom_script_loader_binast_encoding_enabled();
212 #else
213     return false;
214 #endif
215   }
216 
217   static nsresult CompileModule(JSContext* aCx,
218                                 JS::SourceText<char16_t>& aSrcBuf,
219                                 JS::Handle<JSObject*> aEvaluationGlobal,
220                                 JS::CompileOptions& aCompileOptions,
221                                 JS::MutableHandle<JSObject*> aModule);
222 
223   static nsresult CompileModule(JSContext* aCx,
224                                 JS::SourceText<mozilla::Utf8Unit>& aSrcBuf,
225                                 JS::Handle<JSObject*> aEvaluationGlobal,
226                                 JS::CompileOptions& aCompileOptions,
227                                 JS::MutableHandle<JSObject*> aModule);
228 
229   static nsresult ModuleInstantiate(JSContext* aCx,
230                                     JS::Handle<JSObject*> aModule);
231 
232   static nsresult ModuleEvaluate(JSContext* aCx, JS::Handle<JSObject*> aModule);
233 
234   // Returns false if an exception got thrown on aCx.  Passing a null
235   // aElement is allowed; that wil produce an empty aScopeChain.
236   static bool GetScopeChainForElement(
237       JSContext* aCx, mozilla::dom::Element* aElement,
238       JS::MutableHandleVector<JSObject*> aScopeChain);
239 
240   static void ResetTimeZone();
241 
242   static bool DumpEnabled();
243 };
244 
AssignFromStringBuffer(nsStringBuffer * buffer,size_t len,nsAString & dest)245 inline void AssignFromStringBuffer(nsStringBuffer* buffer, size_t len,
246                                    nsAString& dest) {
247   buffer->ToString(len, dest);
248 }
249 
250 template <typename T, typename std::enable_if_t<std::is_same<
251                           typename T::char_type, char16_t>::value>* = nullptr>
AssignJSString(JSContext * cx,T & dest,JSString * s)252 inline bool AssignJSString(JSContext* cx, T& dest, JSString* s) {
253   size_t len = JS::GetStringLength(s);
254   static_assert(js::MaxStringLength < (1 << 30),
255                 "Shouldn't overflow here or in SetCapacity");
256 
257   const char16_t* chars;
258   if (XPCStringConvert::MaybeGetDOMStringChars(s, &chars)) {
259     // The characters represent an existing string buffer that we shared with
260     // JS.  We can share that buffer ourselves if the string corresponds to the
261     // whole buffer; otherwise we have to copy.
262     if (chars[len] == '\0') {
263       AssignFromStringBuffer(
264           nsStringBuffer::FromData(const_cast<char16_t*>(chars)), len, dest);
265       return true;
266     }
267   } else if (XPCStringConvert::MaybeGetLiteralStringChars(s, &chars)) {
268     // The characters represent a literal char16_t string constant
269     // compiled into libxul; we can just use it as-is.
270     dest.AssignLiteral(chars, len);
271     return true;
272   }
273 
274   // We don't bother checking for a dynamic-atom external string, because we'd
275   // just need to copy out of it anyway.
276 
277   if (MOZ_UNLIKELY(!dest.SetLength(len, mozilla::fallible))) {
278     JS_ReportOutOfMemory(cx);
279     return false;
280   }
281   return js::CopyStringChars(cx, dest.BeginWriting(), s, len);
282 }
283 
284 // Specialization for UTF8String.
285 template <typename T, typename std::enable_if_t<std::is_same<
286                           typename T::char_type, char>::value>* = nullptr>
AssignJSString(JSContext * cx,T & dest,JSString * s)287 inline bool AssignJSString(JSContext* cx, T& dest, JSString* s) {
288   using namespace mozilla;
289   CheckedInt<size_t> bufLen(JS::GetStringLength(s));
290   // From the contract for JS_EncodeStringToUTF8BufferPartial, to guarantee that
291   // the whole string is converted.
292   if (js::StringHasLatin1Chars(s)) {
293     bufLen *= 2;
294   } else {
295     bufLen *= 3;
296   }
297 
298   if (MOZ_UNLIKELY(!bufLen.isValid())) {
299     JS_ReportOutOfMemory(cx);
300     return false;
301   }
302 
303   // Shouldn't really matter, but worth being safe.
304   const bool kAllowShrinking = true;
305 
306   nsresult rv;
307   auto handle = dest.BulkWrite(bufLen.value(), 0, kAllowShrinking, rv);
308   if (MOZ_UNLIKELY(NS_FAILED(rv))) {
309     JS_ReportOutOfMemory(cx);
310     return false;
311   }
312 
313   auto maybe = JS_EncodeStringToUTF8BufferPartial(cx, s, handle.AsSpan());
314   if (MOZ_UNLIKELY(!maybe)) {
315     JS_ReportOutOfMemory(cx);
316     return false;
317   }
318 
319   size_t read;
320   size_t written;
321   Tie(read, written) = *maybe;
322 
323   MOZ_ASSERT(read == JS::GetStringLength(s));
324   handle.Finish(written, kAllowShrinking);
325   return true;
326 }
327 
AssignJSLinearString(nsAString & dest,JSLinearString * s)328 inline void AssignJSLinearString(nsAString& dest, JSLinearString* s) {
329   size_t len = js::GetLinearStringLength(s);
330   static_assert(js::MaxStringLength < (1 << 30),
331                 "Shouldn't overflow here or in SetCapacity");
332   dest.SetLength(len);
333   js::CopyLinearStringChars(dest.BeginWriting(), s, len);
334 }
335 
336 template <typename T>
337 class nsTAutoJSString : public nsTAutoString<T> {
338  public:
339   /**
340    * nsTAutoJSString should be default constructed, which leaves it empty
341    * (this->IsEmpty()), and initialized with one of the init() methods below.
342    */
343   nsTAutoJSString() = default;
344 
init(JSContext * aContext,JSString * str)345   bool init(JSContext* aContext, JSString* str) {
346     return AssignJSString(aContext, *this, str);
347   }
348 
init(JSContext * aContext,const JS::Value & v)349   bool init(JSContext* aContext, const JS::Value& v) {
350     if (v.isString()) {
351       return init(aContext, v.toString());
352     }
353 
354     // Stringify, making sure not to run script.
355     JS::Rooted<JSString*> str(aContext);
356     if (v.isObject()) {
357       str = JS_NewStringCopyZ(aContext, "[Object]");
358     } else {
359       JS::Rooted<JS::Value> rootedVal(aContext, v);
360       str = JS::ToString(aContext, rootedVal);
361     }
362 
363     return str && init(aContext, str);
364   }
365 
init(JSContext * aContext,jsid id)366   bool init(JSContext* aContext, jsid id) {
367     JS::Rooted<JS::Value> v(aContext);
368     return JS_IdToValue(aContext, id, &v) && init(aContext, v);
369   }
370 
371   bool init(const JS::Value& v);
372 
373   ~nsTAutoJSString() = default;
374 };
375 
376 using nsAutoJSString = nsTAutoJSString<char16_t>;
377 
378 // Note that this is guaranteed to be UTF-8.
379 using nsAutoJSCString = nsTAutoJSString<char>;
380 
381 #endif /* nsJSUtils_h__ */
382