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/. */
7 #include "mozilla/jni/GeckoBundleUtils.h"
9 #include "JavaBuiltins.h"
10 #include "js/Warnings.h"
11 #include "nsJSUtils.h"
14 #include "js/experimental/TypedData.h"
16 namespace mozilla::jni
{
18 bool CheckJS(JSContext
* aCx
, bool aResult
) {
20 JS_ClearPendingException(aCx
);
25 nsresult
BoxString(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
26 jni::Object::LocalRef
& aOut
) {
27 if (aData
.isNullOrUndefined()) {
32 MOZ_ASSERT(aData
.isString());
34 JS::Rooted
<JSString
*> str(aCx
, aData
.toString());
36 if (JS::StringHasLatin1Chars(str
)) {
37 nsAutoJSString autoStr
;
38 NS_ENSURE_TRUE(CheckJS(aCx
, autoStr
.init(aCx
, str
)), NS_ERROR_FAILURE
);
40 // StringParam can automatically convert a nsString to jstring.
41 aOut
= jni::StringParam(autoStr
, aOut
.Env(), fallible
);
43 return NS_ERROR_FAILURE
;
49 JNIEnv
* const env
= aOut
.Env();
50 const char16_t
* chars
;
52 JS::AutoCheckCannotGC nogc
;
54 chars
= JS_GetTwoByteStringCharsAndLength(aCx
, nogc
, str
, &len
);
56 aOut
= jni::String::LocalRef::Adopt(
57 env
, env
->NewString(reinterpret_cast<const jchar
*>(chars
), len
));
60 if (NS_WARN_IF(!CheckJS(aCx
, !!chars
) || !aOut
)) {
61 env
->ExceptionClear();
62 return NS_ERROR_FAILURE
;
67 nsresult
BoxObject(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
68 jni::Object::LocalRef
& aOut
);
70 template <typename Type
, bool (JS::Value::*IsType
)() const,
71 Type (JS::Value::*ToType
)() const, class ArrayType
,
72 typename
ArrayType::LocalRef (*NewArray
)(const Type
*, size_t)>
73 nsresult
BoxArrayPrimitive(JSContext
* aCx
, JS::Handle
<JSObject
*> aData
,
74 jni::Object::LocalRef
& aOut
, size_t aLength
,
75 JS::Handle
<JS::Value
> aElement
) {
76 JS::Rooted
<JS::Value
> element(aCx
);
77 auto data
= MakeUnique
<Type
[]>(aLength
);
78 data
[0] = (aElement
.get().*ToType
)();
80 for (size_t i
= 1; i
< aLength
; i
++) {
81 NS_ENSURE_TRUE(CheckJS(aCx
, JS_GetElement(aCx
, aData
, i
, &element
)),
83 NS_ENSURE_TRUE((element
.get().*IsType
)(), NS_ERROR_INVALID_ARG
);
85 data
[i
] = (element
.get().*ToType
)();
87 aOut
= (*NewArray
)(data
.get(), aLength
);
91 nsresult
BoxByteArray(JSContext
* aCx
, JS::Handle
<JSObject
*> aData
,
92 jni::Object::LocalRef
& aOut
) {
93 JS::AutoCheckCannotGC nogc
;
94 bool isShared
= false;
95 const void* data
= JS_GetArrayBufferViewData(aData
, &isShared
, nogc
);
96 size_t length
= JS_GetArrayBufferViewByteLength(aData
);
98 aOut
= jni::ByteArray::New(reinterpret_cast<const int8_t*>(data
), length
);
100 return NS_ERROR_FAILURE
;
105 template <class Type
,
106 nsresult (*Box
)(JSContext
*, JS::Handle
<JS::Value
>,
107 jni::Object::LocalRef
&),
109 nsresult
BoxArrayObject(JSContext
* aCx
, JS::Handle
<JSObject
*> aData
,
110 jni::Object::LocalRef
& aOut
, size_t aLength
,
111 JS::Handle
<JS::Value
> aElement
, IsType
&& aIsType
) {
112 auto out
= jni::ObjectArray::New
<Type
>(aLength
);
113 JS::Rooted
<JS::Value
> element(aCx
);
114 jni::Object::LocalRef
jniElement(aOut
.Env());
116 nsresult rv
= (*Box
)(aCx
, aElement
, jniElement
);
117 NS_ENSURE_SUCCESS(rv
, rv
);
118 out
->SetElement(0, jniElement
);
120 for (size_t i
= 1; i
< aLength
; i
++) {
121 NS_ENSURE_TRUE(CheckJS(aCx
, JS_GetElement(aCx
, aData
, i
, &element
)),
123 NS_ENSURE_TRUE(element
.isNullOrUndefined() || aIsType(element
),
124 NS_ERROR_INVALID_ARG
);
126 rv
= (*Box
)(aCx
, element
, jniElement
);
127 NS_ENSURE_SUCCESS(rv
, rv
);
128 out
->SetElement(i
, jniElement
);
134 nsresult
BoxArray(JSContext
* aCx
, JS::Handle
<JSObject
*> aData
,
135 jni::Object::LocalRef
& aOut
) {
137 NS_ENSURE_TRUE(CheckJS(aCx
, JS::GetArrayLength(aCx
, aData
, &length
)),
141 // Always represent empty arrays as an empty boolean array.
142 aOut
= java::GeckoBundle::EMPTY_BOOLEAN_ARRAY();
146 // We only check the first element's type. If the array has mixed types,
147 // we'll throw an error during actual conversion.
148 JS::Rooted
<JS::Value
> element(aCx
);
149 NS_ENSURE_TRUE(CheckJS(aCx
, JS_GetElement(aCx
, aData
, 0, &element
)),
152 if (element
.isBoolean()) {
153 return BoxArrayPrimitive
<bool, &JS::Value::isBoolean
, &JS::Value::toBoolean
,
154 jni::BooleanArray
, &jni::BooleanArray::New
>(
155 aCx
, aData
, aOut
, length
, element
);
158 if (element
.isInt32()) {
160 BoxArrayPrimitive
<int32_t, &JS::Value::isInt32
, &JS::Value::toInt32
,
161 jni::IntArray
, &jni::IntArray::New
>(aCx
, aData
, aOut
,
163 if (rv
!= NS_ERROR_INVALID_ARG
) {
166 // Not int32, but we can still try a double array.
169 if (element
.isNumber()) {
170 return BoxArrayPrimitive
<double, &JS::Value::isNumber
, &JS::Value::toNumber
,
171 jni::DoubleArray
, &jni::DoubleArray::New
>(
172 aCx
, aData
, aOut
, length
, element
);
175 if (element
.isNullOrUndefined() || element
.isString()) {
176 const auto isString
= [](JS::Handle
<JS::Value
> val
) -> bool {
177 return val
.isString();
179 nsresult rv
= BoxArrayObject
<jni::String
, &BoxString
>(
180 aCx
, aData
, aOut
, length
, element
, isString
);
181 if (element
.isString() || rv
!= NS_ERROR_INVALID_ARG
) {
184 // First element was null/undefined, so it may still be an object array.
187 const auto isObject
= [aCx
](JS::Handle
<JS::Value
> val
) -> bool {
188 if (!val
.isObject()) {
192 JS::Rooted
<JSObject
*> obj(aCx
, &val
.toObject());
193 // We don't support array of arrays.
194 return CheckJS(aCx
, JS::IsArrayObject(aCx
, obj
, &array
)) && !array
;
197 if (element
.isNullOrUndefined() || isObject(element
)) {
198 return BoxArrayObject
<java::GeckoBundle
, &BoxObject
>(
199 aCx
, aData
, aOut
, length
, element
, isObject
);
202 NS_WARNING("Unknown type");
203 return NS_ERROR_INVALID_ARG
;
206 nsresult
BoxValue(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
207 jni::Object::LocalRef
& aOut
);
209 nsresult
BoxObject(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
210 jni::Object::LocalRef
& aOut
) {
211 if (aData
.isNullOrUndefined()) {
216 MOZ_ASSERT(aData
.isObject());
218 JS::Rooted
<JS::IdVector
> ids(aCx
, JS::IdVector(aCx
));
219 JS::Rooted
<JSObject
*> obj(aCx
, &aData
.toObject());
221 bool isArray
= false;
222 if (CheckJS(aCx
, JS::IsArrayObject(aCx
, obj
, &isArray
)) && isArray
) {
223 return BoxArray(aCx
, obj
, aOut
);
226 if (JS_IsTypedArrayObject(obj
)) {
227 return BoxByteArray(aCx
, obj
, aOut
);
230 NS_ENSURE_TRUE(CheckJS(aCx
, JS_Enumerate(aCx
, obj
, &ids
)), NS_ERROR_FAILURE
);
232 const size_t length
= ids
.length();
233 auto keys
= jni::ObjectArray::New
<jni::String
>(length
);
234 auto values
= jni::ObjectArray::New
<jni::Object
>(length
);
236 // Iterate through each property of the JS object.
237 for (size_t i
= 0; i
< ids
.length(); i
++) {
238 const JS::RootedId
id(aCx
, ids
[i
]);
239 JS::Rooted
<JS::Value
> idVal(aCx
);
240 JS::Rooted
<JS::Value
> val(aCx
);
241 jni::Object::LocalRef
key(aOut
.Env());
242 jni::Object::LocalRef
value(aOut
.Env());
244 NS_ENSURE_TRUE(CheckJS(aCx
, JS_IdToValue(aCx
, id
, &idVal
)),
247 JS::Rooted
<JSString
*> idStr(aCx
, JS::ToString(aCx
, idVal
));
248 NS_ENSURE_TRUE(CheckJS(aCx
, !!idStr
), NS_ERROR_FAILURE
);
250 idVal
.setString(idStr
);
251 NS_ENSURE_SUCCESS(BoxString(aCx
, idVal
, key
), NS_ERROR_FAILURE
);
252 NS_ENSURE_TRUE(CheckJS(aCx
, JS_GetPropertyById(aCx
, obj
, id
, &val
)),
255 nsresult rv
= BoxValue(aCx
, val
, value
);
256 if (rv
== NS_ERROR_INVALID_ARG
&& !JS_IsExceptionPending(aCx
)) {
257 nsAutoJSString autoStr
;
258 if (CheckJS(aCx
, autoStr
.init(aCx
, idVal
.toString()))) {
259 JS_ReportErrorUTF8(aCx
, "Invalid event data property %s",
260 NS_ConvertUTF16toUTF8(autoStr
).get());
263 NS_ENSURE_SUCCESS(rv
, rv
);
265 keys
->SetElement(i
, key
);
266 values
->SetElement(i
, value
);
269 aOut
= java::GeckoBundle::New(keys
, values
);
273 nsresult
BoxValue(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
274 jni::Object::LocalRef
& aOut
) {
275 if (aData
.isNullOrUndefined()) {
277 } else if (aData
.isBoolean()) {
278 aOut
= aData
.toBoolean() ? java::sdk::Boolean::TRUE()
279 : java::sdk::Boolean::FALSE();
280 } else if (aData
.isInt32()) {
281 aOut
= java::sdk::Integer::ValueOf(aData
.toInt32());
282 } else if (aData
.isNumber()) {
283 aOut
= java::sdk::Double::New(aData
.toNumber());
284 } else if (aData
.isString()) {
285 return BoxString(aCx
, aData
, aOut
);
286 } else if (aData
.isObject()) {
287 return BoxObject(aCx
, aData
, aOut
);
289 NS_WARNING("Unknown type");
290 return NS_ERROR_INVALID_ARG
;
295 } // namespace detail
297 nsresult
BoxData(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
298 jni::Object::LocalRef
& aOut
, bool aObjectOnly
) {
299 nsresult rv
= NS_ERROR_INVALID_ARG
;
302 rv
= detail::BoxValue(aCx
, aData
, aOut
);
303 } else if (aData
.isObject() || aData
.isNullOrUndefined()) {
304 rv
= detail::BoxObject(aCx
, aData
, aOut
);
309 } // namespace mozilla::jni