Bug 1877752 - Remove now-unused use_counter error metric. r=chutten
[gecko.git] / widget / android / jni / GeckoBundleUtils.cpp
blob310f28409390de49fc5b154b35e48ceeb7450a11
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"
13 #include "js/Array.h"
14 #include "js/experimental/TypedData.h"
16 namespace mozilla::jni {
17 namespace detail {
18 bool CheckJS(JSContext* aCx, bool aResult) {
19 if (!aResult) {
20 JS_ClearPendingException(aCx);
22 return aResult;
25 nsresult BoxString(JSContext* aCx, JS::Handle<JS::Value> aData,
26 jni::Object::LocalRef& aOut) {
27 if (aData.isNullOrUndefined()) {
28 aOut = nullptr;
29 return NS_OK;
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);
42 if (!aOut) {
43 return NS_ERROR_FAILURE;
45 return NS_OK;
48 // Two-byte string
49 JNIEnv* const env = aOut.Env();
50 const char16_t* chars;
52 JS::AutoCheckCannotGC nogc;
53 size_t len = 0;
54 chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len);
55 if (chars) {
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;
64 return NS_OK;
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)),
82 NS_ERROR_FAILURE);
83 NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG);
85 data[i] = (element.get().*ToType)();
87 aOut = (*NewArray)(data.get(), aLength);
88 return NS_OK;
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);
99 if (!aOut) {
100 return NS_ERROR_FAILURE;
102 return NS_OK;
105 template <class Type,
106 nsresult (*Box)(JSContext*, JS::Handle<JS::Value>,
107 jni::Object::LocalRef&),
108 typename IsType>
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)),
122 NS_ERROR_FAILURE);
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);
130 aOut = out;
131 return NS_OK;
134 nsresult BoxArray(JSContext* aCx, JS::Handle<JSObject*> aData,
135 jni::Object::LocalRef& aOut) {
136 uint32_t length = 0;
137 NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, aData, &length)),
138 NS_ERROR_FAILURE);
140 if (!length) {
141 // Always represent empty arrays as an empty boolean array.
142 aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY();
143 return NS_OK;
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)),
150 NS_ERROR_FAILURE);
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()) {
159 nsresult rv =
160 BoxArrayPrimitive<int32_t, &JS::Value::isInt32, &JS::Value::toInt32,
161 jni::IntArray, &jni::IntArray::New>(aCx, aData, aOut,
162 length, element);
163 if (rv != NS_ERROR_INVALID_ARG) {
164 return rv;
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) {
182 return rv;
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()) {
189 return false;
191 bool array = false;
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()) {
212 aOut = nullptr;
213 return NS_OK;
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)),
245 NS_ERROR_FAILURE);
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)),
253 NS_ERROR_FAILURE);
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);
270 return NS_OK;
273 nsresult BoxValue(JSContext* aCx, JS::Handle<JS::Value> aData,
274 jni::Object::LocalRef& aOut) {
275 if (aData.isNullOrUndefined()) {
276 aOut = nullptr;
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);
288 } else {
289 NS_WARNING("Unknown type");
290 return NS_ERROR_INVALID_ARG;
292 return NS_OK;
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;
301 if (!aObjectOnly) {
302 rv = detail::BoxValue(aCx, aData, aOut);
303 } else if (aData.isObject() || aData.isNullOrUndefined()) {
304 rv = detail::BoxObject(aCx, aData, aOut);
307 return rv;
309 } // namespace mozilla::jni