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