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 
19 #include "jsapi.h"
20 #include "js/Conversions.h"
21 #include "js/String.h"  // JS::{,Lossy}CopyLinearStringChars, JS::CopyStringChars, JS::Get{,Linear}StringLength, JS::MaxStringLength, JS::StringHasLatin1Chars
22 #include "nsString.h"
23 #include "xpcpublic.h"
24 
25 class nsIScriptContext;
26 class nsIScriptElement;
27 class nsIScriptGlobalObject;
28 class nsXBLPrototypeBinding;
29 
30 namespace mozilla {
31 union Utf8Unit;
32 
33 namespace dom {
34 class AutoJSAPI;
35 class Element;
36 }  // namespace dom
37 }  // namespace mozilla
38 
39 class nsJSUtils {
40  public:
41   static bool GetCallingLocation(JSContext* aContext, nsACString& aFilename,
42                                  uint32_t* aLineno = nullptr,
43                                  uint32_t* aColumn = nullptr);
44   static bool GetCallingLocation(JSContext* aContext, nsAString& aFilename,
45                                  uint32_t* aLineno = nullptr,
46                                  uint32_t* aColumn = nullptr);
47 
48   /**
49    * Retrieve the inner window ID based on the given JSContext.
50    *
51    * @param JSContext aContext
52    *        The JSContext from which you want to find the inner window ID.
53    *
54    * @returns uint64_t the inner window ID.
55    */
56   static uint64_t GetCurrentlyRunningCodeInnerWindowID(JSContext* aContext);
57 
58   static nsresult CompileFunction(mozilla::dom::AutoJSAPI& jsapi,
59                                   JS::HandleVector<JSObject*> aScopeChain,
60                                   JS::CompileOptions& aOptions,
61                                   const nsACString& aName, uint32_t aArgCount,
62                                   const char** aArgArray,
63                                   const nsAString& aBody,
64                                   JSObject** aFunctionObject);
65 
66   static nsresult UpdateFunctionDebugMetadata(
67       mozilla::dom::AutoJSAPI& jsapi, JS::Handle<JSObject*> aFun,
68       JS::CompileOptions& aOptions, JS::Handle<JSString*> aElementAttributeName,
69       JS::Handle<JS::Value> aPrivateValue);
70 
71   static nsresult CompileModule(JSContext* aCx,
72                                 JS::SourceText<char16_t>& aSrcBuf,
73                                 JS::Handle<JSObject*> aEvaluationGlobal,
74                                 JS::CompileOptions& aCompileOptions,
75                                 JS::MutableHandle<JSObject*> aModule);
76 
77   static nsresult CompileModule(JSContext* aCx,
78                                 JS::SourceText<mozilla::Utf8Unit>& aSrcBuf,
79                                 JS::Handle<JSObject*> aEvaluationGlobal,
80                                 JS::CompileOptions& aCompileOptions,
81                                 JS::MutableHandle<JSObject*> aModule);
82 
83   static nsresult ModuleInstantiate(JSContext* aCx,
84                                     JS::Handle<JSObject*> aModule);
85 
86   /*
87    * Wrapper for JSAPI ModuleEvaluate function.
88    *
89    * @param JSContext aCx
90    *        The JSContext where this is executed.
91    * @param JS::Handle<JSObject*> aModule
92    *        The module to be evaluated.
93    * @param JS::Handle<Value*> aResult
94    *        If Top level await is enabled:
95    *          The evaluation promise returned from evaluating the module.
96    *        Otherwise:
97    *          Undefined
98    */
99   static nsresult ModuleEvaluate(JSContext* aCx, JS::Handle<JSObject*> aModule,
100                                  JS::MutableHandle<JS::Value> aResult);
101 
102   // Returns false if an exception got thrown on aCx.  Passing a null
103   // aElement is allowed; that wil produce an empty aScopeChain.
104   static bool GetScopeChainForElement(
105       JSContext* aCx, mozilla::dom::Element* aElement,
106       JS::MutableHandleVector<JSObject*> aScopeChain);
107 
108   static void ResetTimeZone();
109 
110   static bool DumpEnabled();
111 };
112 
AssignFromStringBuffer(nsStringBuffer * buffer,size_t len,nsAString & dest)113 inline void AssignFromStringBuffer(nsStringBuffer* buffer, size_t len,
114                                    nsAString& dest) {
115   buffer->ToString(len, dest);
116 }
117 
118 template <typename T, typename std::enable_if_t<std::is_same<
119                           typename T::char_type, char16_t>::value>* = nullptr>
AssignJSString(JSContext * cx,T & dest,JSString * s)120 inline bool AssignJSString(JSContext* cx, T& dest, JSString* s) {
121   size_t len = JS::GetStringLength(s);
122   static_assert(JS::MaxStringLength < (1 << 30),
123                 "Shouldn't overflow here or in SetCapacity");
124 
125   const char16_t* chars;
126   if (XPCStringConvert::MaybeGetDOMStringChars(s, &chars)) {
127     // The characters represent an existing string buffer that we shared with
128     // JS.  We can share that buffer ourselves if the string corresponds to the
129     // whole buffer; otherwise we have to copy.
130     if (chars[len] == '\0') {
131       AssignFromStringBuffer(
132           nsStringBuffer::FromData(const_cast<char16_t*>(chars)), len, dest);
133       return true;
134     }
135   } else if (XPCStringConvert::MaybeGetLiteralStringChars(s, &chars)) {
136     // The characters represent a literal char16_t string constant
137     // compiled into libxul; we can just use it as-is.
138     dest.AssignLiteral(chars, len);
139     return true;
140   }
141 
142   // We don't bother checking for a dynamic-atom external string, because we'd
143   // just need to copy out of it anyway.
144 
145   if (MOZ_UNLIKELY(!dest.SetLength(len, mozilla::fallible))) {
146     JS_ReportOutOfMemory(cx);
147     return false;
148   }
149   return JS::CopyStringChars(cx, dest.BeginWriting(), s, len);
150 }
151 
152 // Specialization for UTF8String.
153 template <typename T, typename std::enable_if_t<std::is_same<
154                           typename T::char_type, char>::value>* = nullptr>
AssignJSString(JSContext * cx,T & dest,JSString * s)155 inline bool AssignJSString(JSContext* cx, T& dest, JSString* s) {
156   using namespace mozilla;
157   CheckedInt<size_t> bufLen(JS::GetStringLength(s));
158   // From the contract for JS_EncodeStringToUTF8BufferPartial, to guarantee that
159   // the whole string is converted.
160   if (JS::StringHasLatin1Chars(s)) {
161     bufLen *= 2;
162   } else {
163     bufLen *= 3;
164   }
165 
166   if (MOZ_UNLIKELY(!bufLen.isValid())) {
167     JS_ReportOutOfMemory(cx);
168     return false;
169   }
170 
171   // Shouldn't really matter, but worth being safe.
172   const bool kAllowShrinking = true;
173 
174   auto handleOrErr = dest.BulkWrite(bufLen.value(), 0, kAllowShrinking);
175   if (MOZ_UNLIKELY(handleOrErr.isErr())) {
176     JS_ReportOutOfMemory(cx);
177     return false;
178   }
179 
180   auto handle = handleOrErr.unwrap();
181 
182   auto maybe = JS_EncodeStringToUTF8BufferPartial(cx, s, handle.AsSpan());
183   if (MOZ_UNLIKELY(!maybe)) {
184     JS_ReportOutOfMemory(cx);
185     return false;
186   }
187 
188   size_t read;
189   size_t written;
190   Tie(read, written) = *maybe;
191 
192   MOZ_ASSERT(read == JS::GetStringLength(s));
193   handle.Finish(written, kAllowShrinking);
194   return true;
195 }
196 
AssignJSLinearString(nsAString & dest,JSLinearString * s)197 inline void AssignJSLinearString(nsAString& dest, JSLinearString* s) {
198   size_t len = JS::GetLinearStringLength(s);
199   static_assert(JS::MaxStringLength < (1 << 30),
200                 "Shouldn't overflow here or in SetCapacity");
201   dest.SetLength(len);
202   JS::CopyLinearStringChars(dest.BeginWriting(), s, len);
203 }
204 
AssignJSLinearString(nsACString & dest,JSLinearString * s)205 inline void AssignJSLinearString(nsACString& dest, JSLinearString* s) {
206   size_t len = JS::GetLinearStringLength(s);
207   static_assert(JS::MaxStringLength < (1 << 30),
208                 "Shouldn't overflow here or in SetCapacity");
209   dest.SetLength(len);
210   JS::LossyCopyLinearStringChars(dest.BeginWriting(), s, len);
211 }
212 
213 template <typename T>
214 class nsTAutoJSLinearString : public nsTAutoString<T> {
215  public:
nsTAutoJSLinearString(JSLinearString * str)216   explicit nsTAutoJSLinearString(JSLinearString* str) {
217     AssignJSLinearString(*this, str);
218   }
219 };
220 
221 using nsAutoJSLinearString = nsTAutoJSLinearString<char16_t>;
222 using nsAutoJSLinearCString = nsTAutoJSLinearString<char>;
223 
224 template <typename T>
225 class nsTAutoJSString : public nsTAutoString<T> {
226  public:
227   /**
228    * nsTAutoJSString should be default constructed, which leaves it empty
229    * (this->IsEmpty()), and initialized with one of the init() methods below.
230    */
231   nsTAutoJSString() = default;
232 
init(JSContext * aContext,JSString * str)233   bool init(JSContext* aContext, JSString* str) {
234     return AssignJSString(aContext, *this, str);
235   }
236 
init(JSContext * aContext,const JS::Value & v)237   bool init(JSContext* aContext, const JS::Value& v) {
238     if (v.isString()) {
239       return init(aContext, v.toString());
240     }
241 
242     // Stringify, making sure not to run script.
243     JS::Rooted<JSString*> str(aContext);
244     if (v.isObject()) {
245       str = JS_NewStringCopyZ(aContext, "[Object]");
246     } else {
247       JS::Rooted<JS::Value> rootedVal(aContext, v);
248       str = JS::ToString(aContext, rootedVal);
249     }
250 
251     return str && init(aContext, str);
252   }
253 
init(JSContext * aContext,jsid id)254   bool init(JSContext* aContext, jsid id) {
255     JS::Rooted<JS::Value> v(aContext);
256     return JS_IdToValue(aContext, id, &v) && init(aContext, v);
257   }
258 
259   bool init(const JS::Value& v);
260 
261   ~nsTAutoJSString() = default;
262 };
263 
264 using nsAutoJSString = nsTAutoJSString<char16_t>;
265 
266 // Note that this is guaranteed to be UTF-8.
267 using nsAutoJSCString = nsTAutoJSString<char>;
268 
269 #endif /* nsJSUtils_h__ */
270