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