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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_ToJSValue_h
8 #define mozilla_dom_ToJSValue_h
10 #include <cstddef> // for size_t
11 #include <cstdint> // for int32_t, int64_t, uint32_t, uint64_t
12 #include <type_traits> // for is_base_of, enable_if_t, enable_if, is_pointer, is_same, void_t
13 #include <utility> // for forward
14 #include "ErrorList.h" // for nsresult
15 #include "js/Array.h" // for NewArrayObject
16 #include "js/GCVector.h" // for RootedVector, MutableWrappedPtrOperations
17 #include "js/PropertyAndElement.h" // JS_DefineUCProperty
18 #include "js/RootingAPI.h" // for MutableHandle, Rooted, Handle, Heap
19 #include "js/Value.h" // for Value
20 #include "js/ValueArray.h" // for HandleValueArray
21 #include "jsapi.h" // for CurrentGlobalOrNull
22 #include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER1
23 #include "mozilla/UniquePtr.h" // for UniquePtr
24 #include "mozilla/Unused.h" // for Unused
25 #include "mozilla/dom/BindingUtils.h" // for MaybeWrapValue, MaybeWrapObjectOrNullValue, XPCOMObjectToJsval, GetOrCreateDOMReflector
26 #include "mozilla/dom/CallbackObject.h" // for CallbackObject
27 #include "mozilla/dom/Record.h"
28 #include "nsID.h" // for NS_GET_TEMPLATE_IID, nsIID
29 #include "nsISupports.h" // for nsISupports
30 #include "nsStringFwd.h" // for nsAString
31 #include "nsTArrayForwardDeclare.h"
32 #include "xpcObjectHelper.h" // for xpcObjectHelper
34 namespace mozilla::dom
{
38 class WindowProxyHolder
;
39 template <typename TypedArrayType
>
40 class TypedArrayCreator
;
42 // If ToJSValue returns false, it must set an exception on the
46 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const nsAString
& aArgument
,
47 JS::MutableHandle
<JS::Value
> aValue
);
49 // Treats the input as UTF-8, and throws otherwise.
50 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const nsACString
& aArgument
,
51 JS::MutableHandle
<JS::Value
> aValue
);
53 // Accept booleans. But be careful here: if we just have a function that takes
54 // a boolean argument, then any pointer that doesn't match one of our other
55 // signatures/templates will get treated as a boolean, which is clearly not
56 // desirable. So make this a template that only gets used if the argument type
57 // is actually boolean
59 [[nodiscard
]] std::enable_if_t
<std::is_same
<T
, bool>::value
, bool> ToJSValue(
60 JSContext
* aCx
, T aArgument
, JS::MutableHandle
<JS::Value
> aValue
) {
61 // Make sure we're called in a compartment
62 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
64 aValue
.setBoolean(aArgument
);
68 // Accept integer types
69 inline bool ToJSValue(JSContext
* aCx
, int32_t aArgument
,
70 JS::MutableHandle
<JS::Value
> aValue
) {
71 // Make sure we're called in a compartment
72 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
74 aValue
.setInt32(aArgument
);
78 inline bool ToJSValue(JSContext
* aCx
, uint32_t aArgument
,
79 JS::MutableHandle
<JS::Value
> aValue
) {
80 // Make sure we're called in a compartment
81 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
83 aValue
.setNumber(aArgument
);
87 inline bool ToJSValue(JSContext
* aCx
, int64_t aArgument
,
88 JS::MutableHandle
<JS::Value
> aValue
) {
89 // Make sure we're called in a compartment
90 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
92 aValue
.setNumber(double(aArgument
));
96 inline bool ToJSValue(JSContext
* aCx
, uint64_t aArgument
,
97 JS::MutableHandle
<JS::Value
> aValue
) {
98 // Make sure we're called in a compartment
99 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
101 aValue
.setNumber(double(aArgument
));
105 // accept floating point types
106 inline bool ToJSValue(JSContext
* aCx
, float aArgument
,
107 JS::MutableHandle
<JS::Value
> aValue
) {
108 // Make sure we're called in a compartment
109 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
111 aValue
.setNumber(aArgument
);
115 inline bool ToJSValue(JSContext
* aCx
, double aArgument
,
116 JS::MutableHandle
<JS::Value
> aValue
) {
117 // Make sure we're called in a compartment
118 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
120 aValue
.set(JS_NumberValue(aArgument
));
124 // Accept CallbackObjects
125 [[nodiscard
]] inline bool ToJSValue(JSContext
* aCx
, CallbackObject
& aArgument
,
126 JS::MutableHandle
<JS::Value
> aValue
) {
127 // Make sure we're called in a compartment
128 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
130 aValue
.setObjectOrNull(aArgument
.Callback(aCx
));
132 return MaybeWrapValue(aCx
, aValue
);
135 // Accept objects that inherit from nsWrapperCache (e.g. most
138 [[nodiscard
]] std::enable_if_t
<std::is_base_of
<nsWrapperCache
, T
>::value
, bool>
139 ToJSValue(JSContext
* aCx
, T
& aArgument
, JS::MutableHandle
<JS::Value
> aValue
) {
140 // Make sure we're called in a compartment
141 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
143 return GetOrCreateDOMReflector(aCx
, aArgument
, aValue
);
146 // Accept non-refcounted DOM objects that do not inherit from
147 // nsWrapperCache. Refcounted ones would be too much of a footgun:
148 // you could convert them to JS twice and get two different objects.
149 namespace binding_detail
{
151 [[nodiscard
]] std::enable_if_t
<
152 std::is_base_of
<NonRefcountedDOMObject
, T
>::value
, bool>
153 ToJSValueFromPointerHelper(JSContext
* aCx
, T
* aArgument
,
154 JS::MutableHandle
<JS::Value
> aValue
) {
155 // Make sure we're called in a compartment
156 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
158 // This is a cut-down version of
159 // WrapNewBindingNonWrapperCachedObject that doesn't need to deal
160 // with nearly as many cases.
166 JS::Rooted
<JSObject
*> obj(aCx
);
167 if (!aArgument
->WrapObject(aCx
, nullptr, &obj
)) {
171 aValue
.setObject(*obj
);
174 } // namespace binding_detail
176 // We can take a non-refcounted non-wrapper-cached DOM object that lives in a
179 [[nodiscard
]] std::enable_if_t
<
180 std::is_base_of
<NonRefcountedDOMObject
, T
>::value
, bool>
181 ToJSValue(JSContext
* aCx
, UniquePtr
<T
>&& aArgument
,
182 JS::MutableHandle
<JS::Value
> aValue
) {
183 if (!binding_detail::ToJSValueFromPointerHelper(aCx
, aArgument
.get(),
188 // JS object took ownership
189 Unused
<< aArgument
.release();
193 // Accept typed arrays built from appropriate nsTArray values
194 template <typename T
>
196 typename
std::enable_if
<std::is_base_of
<AllTypedArraysBase
, T
>::value
,
198 ToJSValue(JSContext
* aCx
, const TypedArrayCreator
<T
>& aArgument
,
199 JS::MutableHandle
<JS::Value
> aValue
) {
200 // Make sure we're called in a compartment
201 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
203 JSObject
* obj
= aArgument
.Create(aCx
);
207 aValue
.setObject(*obj
);
211 namespace binding_detail
{
212 // Helper type alias for picking a script-exposable non-wrappercached XPIDL
213 // interface to expose to JS code. Falls back to `nsISupports` if the specific
214 // interface type is ambiguous.
215 template <typename T
, typename
= void>
216 struct GetScriptableInterfaceType
{
217 using Type
= nsISupports
;
219 static_assert(std::is_base_of_v
<nsISupports
, T
>,
220 "T must inherit from nsISupports");
222 template <typename T
>
223 struct GetScriptableInterfaceType
<
224 T
, std::void_t
<typename
T::ScriptableInterfaceType
>> {
225 using Type
= typename
T::ScriptableInterfaceType
;
227 static_assert(std::is_base_of_v
<Type
, T
>,
228 "T must inherit from ScriptableInterfaceType");
229 static_assert(std::is_base_of_v
<nsISupports
, Type
>,
230 "ScriptableInterfaceType must inherit from nsISupports");
233 template <typename T
>
234 using ScriptableInterfaceType
= typename GetScriptableInterfaceType
<T
>::Type
;
235 } // namespace binding_detail
237 // Accept objects that inherit from nsISupports but not nsWrapperCache (e.g.
240 [[nodiscard
]] std::enable_if_t
<!std::is_base_of
<nsWrapperCache
, T
>::value
&&
241 !std::is_base_of
<CallbackObject
, T
>::value
&&
242 std::is_base_of
<nsISupports
, T
>::value
,
244 ToJSValue(JSContext
* aCx
, T
& aArgument
, JS::MutableHandle
<JS::Value
> aValue
) {
245 // Make sure we're called in a compartment
246 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
248 xpcObjectHelper
helper(ToSupports(&aArgument
));
249 JS::Rooted
<JSObject
*> scope(aCx
, JS::CurrentGlobalOrNull(aCx
));
251 NS_GET_TEMPLATE_IID(binding_detail::ScriptableInterfaceType
<T
>);
252 return XPCOMObjectToJsval(aCx
, scope
, helper
, &iid
, true, aValue
);
255 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const WindowProxyHolder
& aArgument
,
256 JS::MutableHandle
<JS::Value
> aValue
);
258 // Accept nsRefPtr/nsCOMPtr
259 template <typename T
>
260 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const nsCOMPtr
<T
>& aArgument
,
261 JS::MutableHandle
<JS::Value
> aValue
) {
262 return ToJSValue(aCx
, *aArgument
.get(), aValue
);
265 template <typename T
>
266 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const RefPtr
<T
>& aArgument
,
267 JS::MutableHandle
<JS::Value
> aValue
) {
268 return ToJSValue(aCx
, *aArgument
.get(), aValue
);
271 template <typename T
>
272 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const NonNull
<T
>& aArgument
,
273 JS::MutableHandle
<JS::Value
> aValue
) {
274 return ToJSValue(aCx
, *aArgument
.get(), aValue
);
277 template <typename T
>
278 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const OwningNonNull
<T
>& aArgument
,
279 JS::MutableHandle
<JS::Value
> aValue
) {
280 return ToJSValue(aCx
, *aArgument
.get(), aValue
);
283 // Accept WebIDL dictionaries
285 [[nodiscard
]] std::enable_if_t
<is_dom_dictionary
<T
>, bool> ToJSValue(
286 JSContext
* aCx
, const T
& aArgument
, JS::MutableHandle
<JS::Value
> aValue
) {
287 return aArgument
.ToObjectInternal(aCx
, aValue
);
290 // Accept existing JS values (which may not be same-compartment with us
291 [[nodiscard
]] inline bool ToJSValue(JSContext
* aCx
, const JS::Value
& aArgument
,
292 JS::MutableHandle
<JS::Value
> aValue
) {
293 aValue
.set(aArgument
);
294 return MaybeWrapValue(aCx
, aValue
);
296 [[nodiscard
]] inline bool ToJSValue(JSContext
* aCx
,
297 JS::Handle
<JS::Value
> aArgument
,
298 JS::MutableHandle
<JS::Value
> aValue
) {
299 aValue
.set(aArgument
);
300 return MaybeWrapValue(aCx
, aValue
);
303 // Accept existing JS values on the Heap (which may not be same-compartment with
305 [[nodiscard
]] inline bool ToJSValue(JSContext
* aCx
,
306 const JS::Heap
<JS::Value
>& aArgument
,
307 JS::MutableHandle
<JS::Value
> aValue
) {
308 aValue
.set(aArgument
);
309 return MaybeWrapValue(aCx
, aValue
);
312 // Accept existing rooted JS values (which may not be same-compartment with us
313 [[nodiscard
]] inline bool ToJSValue(JSContext
* aCx
,
314 const JS::Rooted
<JS::Value
>& aArgument
,
315 JS::MutableHandle
<JS::Value
> aValue
) {
316 aValue
.set(aArgument
);
317 return MaybeWrapValue(aCx
, aValue
);
320 // Accept existing rooted JS objects (which may not be same-compartment with
322 [[nodiscard
]] inline bool ToJSValue(JSContext
* aCx
,
323 const JS::Rooted
<JSObject
*>& aArgument
,
324 JS::MutableHandle
<JS::Value
> aValue
) {
325 aValue
.setObjectOrNull(aArgument
);
326 return MaybeWrapObjectOrNullValue(aCx
, aValue
);
329 // Accept nsresult, for use in rejections, and create an XPCOM
330 // exception object representing that nsresult.
331 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, nsresult aArgument
,
332 JS::MutableHandle
<JS::Value
> aValue
);
334 // Accept ErrorResult, for use in rejections, and create an exception
335 // representing the failure. Note, the ErrorResult must indicate a failure
336 // with aArgument.Failure() returning true.
337 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, ErrorResult
&& aArgument
,
338 JS::MutableHandle
<JS::Value
> aValue
);
340 // Accept owning WebIDL unions.
341 template <typename T
>
342 [[nodiscard
]] std::enable_if_t
<is_dom_owning_union
<T
>, bool> ToJSValue(
343 JSContext
* aCx
, const T
& aArgument
, JS::MutableHandle
<JS::Value
> aValue
) {
344 JS::Rooted
<JSObject
*> global(aCx
, JS::CurrentGlobalOrNull(aCx
));
345 return aArgument
.ToJSVal(aCx
, global
, aValue
);
348 // Accept pointers to other things we accept
349 template <typename T
>
350 [[nodiscard
]] std::enable_if_t
<std::is_pointer
<T
>::value
, bool> ToJSValue(
351 JSContext
* aCx
, T aArgument
, JS::MutableHandle
<JS::Value
> aValue
) {
352 return ToJSValue(aCx
, *aArgument
, aValue
);
355 // Accept Promise objects, which need special handling.
356 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, Promise
& aArgument
,
357 JS::MutableHandle
<JS::Value
> aValue
);
359 // Accept arrays (and nested arrays) of other things we accept
360 template <typename T
>
361 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, T
* aArguments
, size_t aLength
,
362 JS::MutableHandle
<JS::Value
> aValue
);
364 template <typename T
>
365 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const nsTArray
<T
>& aArgument
,
366 JS::MutableHandle
<JS::Value
> aValue
) {
367 return ToJSValue(aCx
, aArgument
.Elements(), aArgument
.Length(), aValue
);
370 template <typename T
>
371 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const FallibleTArray
<T
>& aArgument
,
372 JS::MutableHandle
<JS::Value
> aValue
) {
373 return ToJSValue(aCx
, aArgument
.Elements(), aArgument
.Length(), aValue
);
376 template <typename T
, int N
>
377 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const T (&aArgument
)[N
],
378 JS::MutableHandle
<JS::Value
> aValue
) {
379 return ToJSValue(aCx
, aArgument
, N
, aValue
);
382 // Accept arrays of other things we accept
383 template <typename T
>
384 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, T
* aArguments
, size_t aLength
,
385 JS::MutableHandle
<JS::Value
> aValue
) {
386 // Make sure we're called in a compartment
387 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
389 JS::RootedVector
<JS::Value
> v(aCx
);
390 if (!v
.resize(aLength
)) {
393 for (size_t i
= 0; i
< aLength
; ++i
) {
394 if (!ToJSValue(aCx
, aArguments
[i
], v
[i
])) {
398 JSObject
* arrayObj
= JS::NewArrayObject(aCx
, v
);
402 aValue
.setObject(*arrayObj
);
406 // Accept tuple of other things we accept. The result will be a JS array object.
407 template <typename
... Elements
>
408 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
,
409 const std::tuple
<Elements
...>& aArguments
,
410 JS::MutableHandle
<JS::Value
> aValue
) {
411 // Make sure we're called in a compartment
412 MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx
));
414 JS::RootedVector
<JS::Value
> v(aCx
);
415 if (!v
.resize(sizeof...(Elements
))) {
420 auto Callable
= [aCx
, &ok
, &v
, &i
](auto& aElem
) {
421 ok
= ok
&& ToJSValue(aCx
, aElem
, v
[i
++]);
423 std::apply([Callable
](auto&&... args
) { (Callable(args
), ...); }, aArguments
);
428 JSObject
* arrayObj
= JS::NewArrayObject(aCx
, v
);
432 aValue
.setObject(*arrayObj
);
436 // Accept records of other things we accept. N.B. This assumes that
437 // keys are either UTF-8 or UTF-16-ish. See Bug 1706058.
438 template <typename K
, typename V
>
439 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const Record
<K
, V
>& aArgument
,
440 JS::MutableHandle
<JS::Value
> aValue
) {
441 JS::Rooted
<JSObject
*> recordObj(aCx
, JS_NewPlainObject(aCx
));
446 for (auto& entry
: aArgument
.Entries()) {
447 JS::Rooted
<JS::Value
> value(aCx
);
448 if (!ToJSValue(aCx
, entry
.mValue
, &value
)) {
452 if constexpr (std::is_same_v
<nsCString
, decltype(entry
.mKey
)>) {
453 NS_ConvertUTF8toUTF16
expandedKey(entry
.mKey
);
454 if (!JS_DefineUCProperty(aCx
, recordObj
, expandedKey
.BeginReading(),
455 expandedKey
.Length(), value
, JSPROP_ENUMERATE
)) {
459 if (!JS_DefineUCProperty(aCx
, recordObj
, entry
.mKey
.BeginReading(),
460 entry
.mKey
.Length(), value
, JSPROP_ENUMERATE
)) {
466 aValue
.setObject(*recordObj
);
470 template <typename T
>
471 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, const Nullable
<T
>& aArgument
,
472 JS::MutableHandle
<JS::Value
> aValue
) {
473 if (aArgument
.IsNull()) {
478 return ToJSValue(aCx
, aArgument
.Value(), aValue
);
481 } // namespace mozilla::dom
483 #endif /* mozilla_dom_ToJSValue_h */