summaryrefslogtreecommitdiffstats
path: root/widget/android/jni/GeckoBundleUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/android/jni/GeckoBundleUtils.cpp')
-rw-r--r--widget/android/jni/GeckoBundleUtils.cpp309
1 files changed, 309 insertions, 0 deletions
diff --git a/widget/android/jni/GeckoBundleUtils.cpp b/widget/android/jni/GeckoBundleUtils.cpp
new file mode 100644
index 0000000000..310f284093
--- /dev/null
+++ b/widget/android/jni/GeckoBundleUtils.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/jni/GeckoBundleUtils.h"
+
+#include "JavaBuiltins.h"
+#include "js/Warnings.h"
+#include "nsJSUtils.h"
+
+#include "js/Array.h"
+#include "js/experimental/TypedData.h"
+
+namespace mozilla::jni {
+namespace detail {
+bool CheckJS(JSContext* aCx, bool aResult) {
+ if (!aResult) {
+ JS_ClearPendingException(aCx);
+ }
+ return aResult;
+}
+
+nsresult BoxString(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut) {
+ if (aData.isNullOrUndefined()) {
+ aOut = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aData.isString());
+
+ JS::Rooted<JSString*> str(aCx, aData.toString());
+
+ if (JS::StringHasLatin1Chars(str)) {
+ nsAutoJSString autoStr;
+ NS_ENSURE_TRUE(CheckJS(aCx, autoStr.init(aCx, str)), NS_ERROR_FAILURE);
+
+ // StringParam can automatically convert a nsString to jstring.
+ aOut = jni::StringParam(autoStr, aOut.Env(), fallible);
+ if (!aOut) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // Two-byte string
+ JNIEnv* const env = aOut.Env();
+ const char16_t* chars;
+ {
+ JS::AutoCheckCannotGC nogc;
+ size_t len = 0;
+ chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len);
+ if (chars) {
+ aOut = jni::String::LocalRef::Adopt(
+ env, env->NewString(reinterpret_cast<const jchar*>(chars), len));
+ }
+ }
+ if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult BoxObject(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut);
+
+template <typename Type, bool (JS::Value::*IsType)() const,
+ Type (JS::Value::*ToType)() const, class ArrayType,
+ typename ArrayType::LocalRef (*NewArray)(const Type*, size_t)>
+nsresult BoxArrayPrimitive(JSContext* aCx, JS::Handle<JSObject*> aData,
+ jni::Object::LocalRef& aOut, size_t aLength,
+ JS::Handle<JS::Value> aElement) {
+ JS::Rooted<JS::Value> element(aCx);
+ auto data = MakeUnique<Type[]>(aLength);
+ data[0] = (aElement.get().*ToType)();
+
+ for (size_t i = 1; i < aLength; i++) {
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG);
+
+ data[i] = (element.get().*ToType)();
+ }
+ aOut = (*NewArray)(data.get(), aLength);
+ return NS_OK;
+}
+
+nsresult BoxByteArray(JSContext* aCx, JS::Handle<JSObject*> aData,
+ jni::Object::LocalRef& aOut) {
+ JS::AutoCheckCannotGC nogc;
+ bool isShared = false;
+ const void* data = JS_GetArrayBufferViewData(aData, &isShared, nogc);
+ size_t length = JS_GetArrayBufferViewByteLength(aData);
+
+ aOut = jni::ByteArray::New(reinterpret_cast<const int8_t*>(data), length);
+ if (!aOut) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+template <class Type,
+ nsresult (*Box)(JSContext*, JS::Handle<JS::Value>,
+ jni::Object::LocalRef&),
+ typename IsType>
+nsresult BoxArrayObject(JSContext* aCx, JS::Handle<JSObject*> aData,
+ jni::Object::LocalRef& aOut, size_t aLength,
+ JS::Handle<JS::Value> aElement, IsType&& aIsType) {
+ auto out = jni::ObjectArray::New<Type>(aLength);
+ JS::Rooted<JS::Value> element(aCx);
+ jni::Object::LocalRef jniElement(aOut.Env());
+
+ nsresult rv = (*Box)(aCx, aElement, jniElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out->SetElement(0, jniElement);
+
+ for (size_t i = 1; i < aLength; i++) {
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element),
+ NS_ERROR_INVALID_ARG);
+
+ rv = (*Box)(aCx, element, jniElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out->SetElement(i, jniElement);
+ }
+ aOut = out;
+ return NS_OK;
+}
+
+nsresult BoxArray(JSContext* aCx, JS::Handle<JSObject*> aData,
+ jni::Object::LocalRef& aOut) {
+ uint32_t length = 0;
+ NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, aData, &length)),
+ NS_ERROR_FAILURE);
+
+ if (!length) {
+ // Always represent empty arrays as an empty boolean array.
+ aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY();
+ return NS_OK;
+ }
+
+ // We only check the first element's type. If the array has mixed types,
+ // we'll throw an error during actual conversion.
+ JS::Rooted<JS::Value> element(aCx);
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)),
+ NS_ERROR_FAILURE);
+
+ if (element.isBoolean()) {
+ return BoxArrayPrimitive<bool, &JS::Value::isBoolean, &JS::Value::toBoolean,
+ jni::BooleanArray, &jni::BooleanArray::New>(
+ aCx, aData, aOut, length, element);
+ }
+
+ if (element.isInt32()) {
+ nsresult rv =
+ BoxArrayPrimitive<int32_t, &JS::Value::isInt32, &JS::Value::toInt32,
+ jni::IntArray, &jni::IntArray::New>(aCx, aData, aOut,
+ length, element);
+ if (rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ // Not int32, but we can still try a double array.
+ }
+
+ if (element.isNumber()) {
+ return BoxArrayPrimitive<double, &JS::Value::isNumber, &JS::Value::toNumber,
+ jni::DoubleArray, &jni::DoubleArray::New>(
+ aCx, aData, aOut, length, element);
+ }
+
+ if (element.isNullOrUndefined() || element.isString()) {
+ const auto isString = [](JS::Handle<JS::Value> val) -> bool {
+ return val.isString();
+ };
+ nsresult rv = BoxArrayObject<jni::String, &BoxString>(
+ aCx, aData, aOut, length, element, isString);
+ if (element.isString() || rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ // First element was null/undefined, so it may still be an object array.
+ }
+
+ const auto isObject = [aCx](JS::Handle<JS::Value> val) -> bool {
+ if (!val.isObject()) {
+ return false;
+ }
+ bool array = false;
+ JS::Rooted<JSObject*> obj(aCx, &val.toObject());
+ // We don't support array of arrays.
+ return CheckJS(aCx, JS::IsArrayObject(aCx, obj, &array)) && !array;
+ };
+
+ if (element.isNullOrUndefined() || isObject(element)) {
+ return BoxArrayObject<java::GeckoBundle, &BoxObject>(
+ aCx, aData, aOut, length, element, isObject);
+ }
+
+ NS_WARNING("Unknown type");
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsresult BoxValue(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut);
+
+nsresult BoxObject(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut) {
+ if (aData.isNullOrUndefined()) {
+ aOut = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aData.isObject());
+
+ JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+ JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
+
+ bool isArray = false;
+ if (CheckJS(aCx, JS::IsArrayObject(aCx, obj, &isArray)) && isArray) {
+ return BoxArray(aCx, obj, aOut);
+ }
+
+ if (JS_IsTypedArrayObject(obj)) {
+ return BoxByteArray(aCx, obj, aOut);
+ }
+
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_Enumerate(aCx, obj, &ids)), NS_ERROR_FAILURE);
+
+ const size_t length = ids.length();
+ auto keys = jni::ObjectArray::New<jni::String>(length);
+ auto values = jni::ObjectArray::New<jni::Object>(length);
+
+ // Iterate through each property of the JS object.
+ for (size_t i = 0; i < ids.length(); i++) {
+ const JS::RootedId id(aCx, ids[i]);
+ JS::Rooted<JS::Value> idVal(aCx);
+ JS::Rooted<JS::Value> val(aCx);
+ jni::Object::LocalRef key(aOut.Env());
+ jni::Object::LocalRef value(aOut.Env());
+
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_IdToValue(aCx, id, &idVal)),
+ NS_ERROR_FAILURE);
+
+ JS::Rooted<JSString*> idStr(aCx, JS::ToString(aCx, idVal));
+ NS_ENSURE_TRUE(CheckJS(aCx, !!idStr), NS_ERROR_FAILURE);
+
+ idVal.setString(idStr);
+ NS_ENSURE_SUCCESS(BoxString(aCx, idVal, key), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(CheckJS(aCx, JS_GetPropertyById(aCx, obj, id, &val)),
+ NS_ERROR_FAILURE);
+
+ nsresult rv = BoxValue(aCx, val, value);
+ if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) {
+ nsAutoJSString autoStr;
+ if (CheckJS(aCx, autoStr.init(aCx, idVal.toString()))) {
+ JS_ReportErrorUTF8(aCx, "Invalid event data property %s",
+ NS_ConvertUTF16toUTF8(autoStr).get());
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ keys->SetElement(i, key);
+ values->SetElement(i, value);
+ }
+
+ aOut = java::GeckoBundle::New(keys, values);
+ return NS_OK;
+}
+
+nsresult BoxValue(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut) {
+ if (aData.isNullOrUndefined()) {
+ aOut = nullptr;
+ } else if (aData.isBoolean()) {
+ aOut = aData.toBoolean() ? java::sdk::Boolean::TRUE()
+ : java::sdk::Boolean::FALSE();
+ } else if (aData.isInt32()) {
+ aOut = java::sdk::Integer::ValueOf(aData.toInt32());
+ } else if (aData.isNumber()) {
+ aOut = java::sdk::Double::New(aData.toNumber());
+ } else if (aData.isString()) {
+ return BoxString(aCx, aData, aOut);
+ } else if (aData.isObject()) {
+ return BoxObject(aCx, aData, aOut);
+ } else {
+ NS_WARNING("Unknown type");
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_OK;
+}
+
+} // namespace detail
+
+nsresult BoxData(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut, bool aObjectOnly) {
+ nsresult rv = NS_ERROR_INVALID_ARG;
+
+ if (!aObjectOnly) {
+ rv = detail::BoxValue(aCx, aData, aOut);
+ } else if (aData.isObject() || aData.isNullOrUndefined()) {
+ rv = detail::BoxObject(aCx, aData, aOut);
+ }
+
+ return rv;
+}
+} // namespace mozilla::jni