summaryrefslogtreecommitdiffstats
path: root/widget/android/jni
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /widget/android/jni
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/android/jni')
-rw-r--r--widget/android/jni/Accessors.h251
-rw-r--r--widget/android/jni/Conversions.cpp115
-rw-r--r--widget/android/jni/Conversions.h23
-rw-r--r--widget/android/jni/GeckoBundleUtils.cpp309
-rw-r--r--widget/android/jni/GeckoBundleUtils.h46
-rw-r--r--widget/android/jni/GeckoResultUtils.h54
-rw-r--r--widget/android/jni/Natives.h1540
-rw-r--r--widget/android/jni/NativesInlines.h116
-rw-r--r--widget/android/jni/Refs.h1135
-rw-r--r--widget/android/jni/TypeAdapter.h71
-rw-r--r--widget/android/jni/Types.h123
-rw-r--r--widget/android/jni/Utils.cpp348
-rw-r--r--widget/android/jni/Utils.h150
-rw-r--r--widget/android/jni/moz.build36
14 files changed, 4317 insertions, 0 deletions
diff --git a/widget/android/jni/Accessors.h b/widget/android/jni/Accessors.h
new file mode 100644
index 0000000000..7496cbcb5a
--- /dev/null
+++ b/widget/android/jni/Accessors.h
@@ -0,0 +1,251 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_Accessors_h__
+#define mozilla_jni_Accessors_h__
+
+#include <jni.h>
+
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "mozilla/jni/Utils.h"
+#include "AndroidBridge.h"
+
+namespace mozilla {
+namespace jni {
+
+namespace detail {
+
+// Helper class to convert an arbitrary type to a jvalue, e.g. Value(123).val.
+struct Value {
+ explicit Value(jboolean z) { val.z = z; }
+ explicit Value(jbyte b) { val.b = b; }
+ explicit Value(jchar c) { val.c = c; }
+ explicit Value(jshort s) { val.s = s; }
+ explicit Value(jint i) { val.i = i; }
+ explicit Value(jlong j) { val.j = j; }
+ explicit Value(jfloat f) { val.f = f; }
+ explicit Value(jdouble d) { val.d = d; }
+ explicit Value(jobject l) { val.l = l; }
+
+ jvalue val;
+};
+
+} // namespace detail
+
+using namespace detail;
+
+// Base class for Method<>, Field<>, and Constructor<>.
+class Accessor {
+ static void GetNsresult(JNIEnv* env, nsresult* rv) {
+ if (env->ExceptionCheck()) {
+#ifdef MOZ_CHECK_JNI
+ env->ExceptionDescribe();
+#endif
+ env->ExceptionClear();
+ *rv = NS_ERROR_FAILURE;
+ } else {
+ *rv = NS_OK;
+ }
+ }
+
+ protected:
+ // Called after making a JNIEnv call.
+ template <class Traits>
+ static void EndAccess(const typename Traits::Owner::Context& ctx,
+ nsresult* rv) {
+ if (Traits::exceptionMode == ExceptionMode::ABORT) {
+ MOZ_CATCH_JNI_EXCEPTION(ctx.Env());
+
+ } else if (Traits::exceptionMode == ExceptionMode::NSRESULT) {
+ GetNsresult(ctx.Env(), rv);
+ }
+ }
+};
+
+// Member<> is used to call a JNI method given a traits class.
+template <class Traits, typename ReturnType = typename Traits::ReturnType>
+class Method : public Accessor {
+ typedef Accessor Base;
+ typedef typename Traits::Owner::Context Context;
+
+ protected:
+ static jmethodID sID;
+
+ static void BeginAccess(const Context& ctx) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+ static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT,
+ "Dispatching not supported for method call");
+
+ if (sID) {
+ return;
+ }
+
+ if (Traits::isStatic) {
+ MOZ_ALWAYS_TRUE(
+ sID = AndroidBridge::GetStaticMethodID(
+ ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature));
+ } else {
+ MOZ_ALWAYS_TRUE(
+ sID = AndroidBridge::GetMethodID(ctx.Env(), ctx.ClassRef(),
+ Traits::name, Traits::signature));
+ }
+ }
+
+ static void EndAccess(const Context& ctx, nsresult* rv) {
+ return Base::EndAccess<Traits>(ctx, rv);
+ }
+
+ public:
+ template <typename... Args>
+ static ReturnType Call(const Context& ctx, nsresult* rv,
+ const Args&... args) {
+ JNIEnv* const env = ctx.Env();
+ BeginAccess(ctx);
+
+ jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};
+
+ auto result = TypeAdapter<ReturnType>::ToNative(
+ env, Traits::isStatic ? (env->*TypeAdapter<ReturnType>::StaticCall)(
+ ctx.ClassRef(), sID, jargs)
+ : (env->*TypeAdapter<ReturnType>::Call)(
+ ctx.Get(), sID, jargs));
+
+ EndAccess(ctx, rv);
+ return result;
+ }
+};
+
+// Define sID member.
+template <class T, typename R>
+jmethodID Method<T, R>::sID;
+
+// Specialize void because C++ forbids us from
+// using a "void" temporary result variable.
+template <class Traits>
+class Method<Traits, void> : public Method<Traits, bool> {
+ typedef Method<Traits, bool> Base;
+ typedef typename Traits::Owner::Context Context;
+
+ public:
+ template <typename... Args>
+ static void Call(const Context& ctx, nsresult* rv, const Args&... args) {
+ JNIEnv* const env = ctx.Env();
+ Base::BeginAccess(ctx);
+
+ jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};
+
+ if (Traits::isStatic) {
+ env->CallStaticVoidMethodA(ctx.ClassRef(), Base::sID, jargs);
+ } else {
+ env->CallVoidMethodA(ctx.Get(), Base::sID, jargs);
+ }
+
+ Base::EndAccess(ctx, rv);
+ }
+};
+
+// Constructor<> is used to construct a JNI instance given a traits class.
+template <class Traits>
+class Constructor : protected Method<Traits, typename Traits::ReturnType> {
+ typedef typename Traits::Owner::Context Context;
+ typedef typename Traits::ReturnType ReturnType;
+ typedef Method<Traits, ReturnType> Base;
+
+ public:
+ template <typename... Args>
+ static ReturnType Call(const Context& ctx, nsresult* rv,
+ const Args&... args) {
+ JNIEnv* const env = ctx.Env();
+ Base::BeginAccess(ctx);
+
+ jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};
+
+ auto result = TypeAdapter<ReturnType>::ToNative(
+ env, env->NewObjectA(ctx.ClassRef(), Base::sID, jargs));
+
+ Base::EndAccess(ctx, rv);
+ return result;
+ }
+};
+
+// Field<> is used to access a JNI field given a traits class.
+template <class Traits>
+class Field : public Accessor {
+ typedef Accessor Base;
+ typedef typename Traits::Owner::Context Context;
+ typedef typename Traits::ReturnType GetterType;
+ typedef typename Traits::SetterType SetterType;
+
+ private:
+ static jfieldID sID;
+
+ static void BeginAccess(const Context& ctx) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+ static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT,
+ "Dispatching not supported for field access");
+
+ if (sID) {
+ return;
+ }
+
+ if (Traits::isStatic) {
+ MOZ_ALWAYS_TRUE(
+ sID = AndroidBridge::GetStaticFieldID(
+ ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature));
+ } else {
+ MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetFieldID(ctx.Env(), ctx.ClassRef(),
+ Traits::name,
+ Traits::signature));
+ }
+ }
+
+ static void EndAccess(const Context& ctx, nsresult* rv) {
+ return Base::EndAccess<Traits>(ctx, rv);
+ }
+
+ public:
+ static GetterType Get(const Context& ctx, nsresult* rv) {
+ JNIEnv* const env = ctx.Env();
+ BeginAccess(ctx);
+
+ auto result = TypeAdapter<GetterType>::ToNative(
+ env, Traits::isStatic
+ ?
+
+ (env->*TypeAdapter<GetterType>::StaticGet)(ctx.ClassRef(), sID)
+ :
+
+ (env->*TypeAdapter<GetterType>::Get)(ctx.Get(), sID));
+
+ EndAccess(ctx, rv);
+ return result;
+ }
+
+ static void Set(const Context& ctx, nsresult* rv, SetterType val) {
+ JNIEnv* const env = ctx.Env();
+ BeginAccess(ctx);
+
+ if (Traits::isStatic) {
+ (env->*TypeAdapter<SetterType>::StaticSet)(
+ ctx.ClassRef(), sID, TypeAdapter<SetterType>::FromNative(env, val));
+ } else {
+ (env->*TypeAdapter<SetterType>::Set)(
+ ctx.Get(), sID, TypeAdapter<SetterType>::FromNative(env, val));
+ }
+
+ EndAccess(ctx, rv);
+ }
+};
+
+// Define sID member.
+template <class T>
+jfieldID Field<T>::sID;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Accessors_h__
diff --git a/widget/android/jni/Conversions.cpp b/widget/android/jni/Conversions.cpp
new file mode 100644
index 0000000000..f7742b9535
--- /dev/null
+++ b/widget/android/jni/Conversions.cpp
@@ -0,0 +1,115 @@
+/* -*- 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 "Conversions.h"
+#include "JavaBuiltins.h"
+
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+namespace mozilla {
+namespace jni {
+
+template <class T>
+jfieldID GetValueFieldID(JNIEnv* aEnv, const char* aType) {
+ const jfieldID id = aEnv->GetFieldID(
+ typename T::Context(aEnv, nullptr).ClassRef(), "value", aType);
+ aEnv->ExceptionClear();
+ return id;
+}
+
+// Cached locations of the primitive types within their standard boxed objects
+// to skip doing that lookup on every get.
+static jfieldID gBooleanValueField;
+static jfieldID gIntValueField;
+static jfieldID gDoubleValueField;
+
+void InitConversionStatics() {
+ MOZ_ASSERT(NS_IsMainThread());
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ gBooleanValueField = GetValueFieldID<java::sdk::Boolean>(env, "Z");
+ gIntValueField = GetValueFieldID<java::sdk::Integer>(env, "I");
+ gDoubleValueField = GetValueFieldID<java::sdk::Double>(env, "D");
+}
+
+template <>
+bool Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Boolean>());
+
+ bool result = false;
+ if (gBooleanValueField) {
+ if (!aEnv) {
+ aEnv = jni::GetEnvForThread();
+ }
+ result =
+ aEnv->GetBooleanField(aData.Get(), gBooleanValueField) != JNI_FALSE;
+ MOZ_CATCH_JNI_EXCEPTION(aEnv);
+ } else {
+ result = java::sdk::Boolean::Ref::From(aData)->BooleanValue();
+ }
+
+ return result;
+}
+
+template <>
+int Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Integer>());
+
+ int result = 0;
+ if (gIntValueField) {
+ if (!aEnv) {
+ aEnv = jni::GetEnvForThread();
+ }
+ result = aEnv->GetIntField(aData.Get(), gIntValueField);
+ MOZ_CATCH_JNI_EXCEPTION(aEnv);
+ } else {
+ result = java::sdk::Number::Ref::From(aData)->IntValue();
+ }
+
+ return result;
+}
+
+template <>
+double Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Double>());
+
+ double result = 0;
+ if (gDoubleValueField) {
+ if (!aEnv) {
+ aEnv = jni::GetEnvForThread();
+ }
+ result = aEnv->GetDoubleField(aData.Get(), gDoubleValueField);
+ MOZ_CATCH_JNI_EXCEPTION(aEnv);
+ } else {
+ result = java::sdk::Number::Ref::From(aData)->DoubleValue();
+ }
+
+ return result;
+}
+
+template <>
+ipc::LaunchError Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ // Bug 1819311: there is not much we can really catch due to how Android
+ // services are started, so for now we just expose it this way.
+ return ipc::LaunchError("Java2Native");
+}
+
+template <>
+nsString Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ nsString result;
+ if (aData != NULL && aData.IsInstanceOf<jni::String>()) {
+ result = jni::String::Ref::From(aData)->ToString();
+ }
+ return result;
+}
+
+template <>
+nsresult Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Throwable>());
+ return NS_ERROR_FAILURE;
+}
+
+} // namespace jni
+} // namespace mozilla
diff --git a/widget/android/jni/Conversions.h b/widget/android/jni/Conversions.h
new file mode 100644
index 0000000000..1d9e20acc7
--- /dev/null
+++ b/widget/android/jni/Conversions.h
@@ -0,0 +1,23 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_Conversions_h__
+#define mozilla_jni_Conversions_h__
+
+#include "mozilla/jni/Refs.h"
+
+namespace mozilla {
+namespace jni {
+
+template <typename ArgType>
+ArgType Java2Native(mozilla::jni::Object::Param, JNIEnv* aEnv = nullptr);
+
+void InitConversionStatics();
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Conversions_h__
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
diff --git a/widget/android/jni/GeckoBundleUtils.h b/widget/android/jni/GeckoBundleUtils.h
new file mode 100644
index 0000000000..a92a27abc3
--- /dev/null
+++ b/widget/android/jni/GeckoBundleUtils.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_GeckoBundleUtils_h
+#define mozilla_jni_GeckoBundleUtils_h
+
+#include "mozilla/java/GeckoBundleWrappers.h"
+
+#include "jsapi.h"
+
+namespace mozilla {
+namespace jni {
+
+#define GECKOBUNDLE_START(name) \
+ nsTArray<jni::String::LocalRef> _##name##_keys; \
+ nsTArray<jni::Object::LocalRef> _##name##_values;
+
+#define GECKOBUNDLE_PUT(name, key, value) \
+ _##name##_keys.AppendElement( \
+ jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING(key))); \
+ _##name##_values.AppendElement(value);
+
+#define GECKOBUNDLE_FINISH(name) \
+ MOZ_ASSERT(_##name##_keys.Length() == _##name##_values.Length()); \
+ auto _##name##_jkeys = \
+ jni::ObjectArray::New<jni::String>(_##name##_keys.Length()); \
+ auto _##name##_jvalues = \
+ jni::ObjectArray::New<jni::Object>(_##name##_values.Length()); \
+ for (size_t i = 0; \
+ i < _##name##_keys.Length() && i < _##name##_values.Length(); i++) { \
+ _##name##_jkeys->SetElement(i, _##name##_keys.ElementAt(i)); \
+ _##name##_jvalues->SetElement(i, _##name##_values.ElementAt(i)); \
+ } \
+ auto name = \
+ mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues);
+
+nsresult BoxData(JSContext* aCx, JS::Handle<JS::Value> aData,
+ jni::Object::LocalRef& aOut, bool aObjectOnly);
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_GeckoBundleUtils_h
diff --git a/widget/android/jni/GeckoResultUtils.h b/widget/android/jni/GeckoResultUtils.h
new file mode 100644
index 0000000000..da103f35ee
--- /dev/null
+++ b/widget/android/jni/GeckoResultUtils.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_GeckoResultUtils_h
+#define mozilla_jni_GeckoResultUtils_h
+
+#include "mozilla/java/GeckoResultNatives.h"
+#include "mozilla/jni/Conversions.h"
+
+namespace mozilla {
+namespace jni {
+
+// C++-side object bound to Java's GeckoResult.GeckoCallback.
+//
+// Note that we can't template this class because that breaks JNI dispatch
+// (surprisingly: it compiles, but selects the wrong method specialization
+// during dispatch). So instead we use a templated factory function, which
+// bundles the per-ArgType conversion logic into the callback.
+class GeckoResultCallback final
+ : public java::GeckoResult::GeckoCallback::Natives<GeckoResultCallback> {
+ public:
+ typedef java::GeckoResult::GeckoCallback::Natives<GeckoResultCallback> Base;
+ typedef std::function<void(mozilla::jni::Object::Param)> OuterCallback;
+
+ void Call(mozilla::jni::Object::Param aArg) { mCallback(aArg); }
+
+ template <typename ArgType>
+ static java::GeckoResult::GeckoCallback::LocalRef CreateAndAttach(
+ std::function<void(ArgType)>&& aInnerCallback) {
+ auto java = java::GeckoResult::GeckoCallback::New();
+ OuterCallback outerCallback =
+ [inner{std::move(aInnerCallback)}](mozilla::jni::Object::Param aParam) {
+ ArgType converted = Java2Native<ArgType>(aParam);
+ inner(std::move(converted));
+ };
+ auto native = MakeUnique<GeckoResultCallback>(std::move(outerCallback));
+ Base::AttachNative(java, std::move(native));
+ return java;
+ }
+
+ explicit GeckoResultCallback(OuterCallback&& aCallback)
+ : mCallback(std::move(aCallback)) {}
+
+ private:
+ OuterCallback mCallback;
+};
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_GeckoResultUtils_h
diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h
new file mode 100644
index 0000000000..45b351ba21
--- /dev/null
+++ b/widget/android/jni/Natives.h
@@ -0,0 +1,1540 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_Natives_h__
+#define mozilla_jni_Natives_h__
+
+#include <jni.h>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/jni/Accessors.h"
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "mozilla/jni/Utils.h"
+#include "nsThreadUtils.h"
+
+#if defined(_MSC_VER) // MSVC
+# define FUNCTION_SIGNATURE __FUNCSIG__
+#elif defined(__GNUC__) // GCC, Clang
+# define FUNCTION_SIGNATURE __PRETTY_FUNCTION__
+#endif
+
+struct NativeException {
+ const char* str;
+};
+
+template <class T>
+static NativeException NullHandle() {
+ return {FUNCTION_SIGNATURE};
+}
+
+template <class T>
+static NativeException NullWeakPtr() {
+ return {FUNCTION_SIGNATURE};
+}
+
+namespace mozilla {
+
+template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+class MozPromise;
+
+namespace jni {
+
+/**
+ * C++ classes implementing instance (non-static) native methods can choose
+ * from one of two ownership models, when associating a C++ object with a Java
+ * instance.
+ *
+ * * If the C++ class inherits from mozilla::SupportsWeakPtr, weak pointers
+ * will be used. The Java instance will store and own the pointer to a
+ * WeakPtr object. The C++ class itself is otherwise not owned or directly
+ * referenced. Note that mozilla::SupportsWeakPtr only supports being used on
+ * a single thread. To attach a Java instance to a C++ instance, pass in a
+ * mozilla::SupportsWeakPtr pointer to the C++ class (i.e. MyClass*).
+ *
+ * class MyClass : public SupportsWeakPtr
+ * , public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * using MyJavaClass::Natives<MyClass>::DisposeNative;
+ *
+ * void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachNative(
+ * instance, static_cast<SupportsWeakPtr*>(this));
+ *
+ * // "instance" does NOT own "this", so the C++ object
+ * // lifetime is separate from the Java object lifetime.
+ * }
+ * };
+ *
+ * * If the C++ class contains public members AddRef() and Release(), the Java
+ * instance will store and own the pointer to a RefPtr object, which holds a
+ * strong reference on the C++ instance. Normal ref-counting considerations
+ * apply in this case; for example, disposing may cause the C++ instance to
+ * be deleted and the destructor to be run on the current thread, which may
+ * not be desirable. To attach a Java instance to a C++ instance, pass in a
+ * pointer to the C++ class (i.e. MyClass*).
+ *
+ * class MyClass : public RefCounted<MyClass>
+ * , public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * using MyJavaClass::Natives<MyClass>::DisposeNative;
+ *
+ * void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachNative(instance, this);
+ *
+ * // "instance" owns "this" through the RefPtr, so the C++ object
+ * // may be destroyed as soon as instance.disposeNative() is called.
+ * }
+ * };
+ *
+ * * In other cases, the Java instance will store and own a pointer to the C++
+ * object itself. This pointer must not be stored or deleted elsewhere. To
+ * attach a Java instance to a C++ instance, pass in a reference to a
+ * UniquePtr of the C++ class (i.e. UniquePtr<MyClass>).
+ *
+ * class MyClass : public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * using MyJavaClass::Natives<MyClass>::DisposeNative;
+ *
+ * static void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachNative(
+ * instance, mozilla::MakeUnique<MyClass>());
+ *
+ * // "instance" owns the newly created C++ object, so the C++
+ * // object is destroyed as soon as instance.disposeNative() is
+ * // called.
+ * }
+ * };
+ */
+
+namespace detail {
+
+/**
+ * Type trait that determines whether a given class has a member named
+ * T::OnWeakNonIntrusiveDetach.
+ *
+ * Example usage:
+ * class Foo {};
+ * class Bar {
+ * public:
+ * void OnWeakNonIntrusiveDetach(already_AddRefed<nsIRunnable> aRunnable);
+ * };
+ *
+ * constexpr bool foo = HasWeakNonIntrusiveDetach<Foo>::value; // Expect false
+ * constexpr bool bar = HasWeakNonIntrusiveDetach<Bar>::value; // Expect true
+ */
+template <typename, typename = std::void_t<>>
+struct HasWeakNonIntrusiveDetach : std::false_type {};
+
+template <typename T>
+struct HasWeakNonIntrusiveDetach<
+ T, std::void_t<decltype(std::declval<T>().OnWeakNonIntrusiveDetach(
+ std::declval<already_AddRefed<nsIRunnable>>()))>> : std::true_type {
+};
+
+/**
+ * Type trait that determines whether a given class is refcounted, ie. it has
+ * both T::AddRef and T::Release methods.
+ *
+ * Example usage:
+ * class Foo {};
+ * class Bar {
+ * public:
+ * void AddRef();
+ * void Release();
+ * };
+ *
+ * constexpr bool foo = IsRefCounted<Foo>::value; // Expect false
+ * constexpr bool bar = IsRefCounted<Bar>::value; // Expect true
+ */
+template <typename, typename = std::void_t<>>
+struct IsRefCounted : std::false_type {};
+
+template <typename T>
+struct IsRefCounted<T, std::void_t<decltype(std::declval<T>().AddRef(),
+ std::declval<T>().Release())>>
+ : std::true_type {};
+
+/**
+ * This enum is used for classifying the type of pointer that is stored
+ * within a NativeWeakPtr. This classification is different from the one used
+ * for normal native pointers.
+ */
+enum class NativePtrInternalType : size_t {
+ OWNING = 1,
+ WEAK = 2,
+ REFPTR = 3,
+};
+
+/**
+ * NativePtrInternalPicker uses some C++ SFINAE template-fu to figure out
+ * what type of pointer the class specified by Impl needs to be.
+ *
+ * It does this by supplying multiple overloads of a method named Test.
+ * Various overloads are enabled or disabled depending on whether or not Impl
+ * can possibly support them.
+ *
+ * Each overload "returns" a reference to an array whose size corresponds to the
+ * value of each enum in NativePtrInternalType. That size is then converted back
+ * to the enum value, yielding the right type.
+ */
+template <class Impl>
+class NativePtrInternalPicker {
+ // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK
+ template <class I>
+ static std::enable_if_t<
+ std::is_base_of<SupportsWeakPtr, I>::value,
+ char (&)[static_cast<size_t>(NativePtrInternalType::WEAK)]>
+ Test(char);
+
+ // Enable if Impl implements AddRef and Release, yielding type REFPTR
+ template <class I, typename = decltype(&I::AddRef, &I::Release)>
+ static char (&Test(int))[static_cast<size_t>(NativePtrInternalType::REFPTR)];
+
+ // This overload uses '...' as its param to make its arguments less specific;
+ // the compiler prefers more-specific overloads to less-specific ones.
+ // OWNING is the fallback type.
+ template <class>
+ static char (&Test(...))[static_cast<size_t>(NativePtrInternalType::OWNING)];
+
+ public:
+ // Given a hypothetical function call Test<Impl>, convert the size of its
+ // resulting array back into a NativePtrInternalType enum value.
+ static const NativePtrInternalType value = static_cast<NativePtrInternalType>(
+ sizeof(Test<Impl>('\0')) / sizeof(char));
+};
+
+/**
+ * This enum is used for classifying the type of pointer that is stored in a
+ * JNIObject's handle.
+ *
+ * We have two different weak pointer types:
+ * * WEAK_INTRUSIVE is a pointer to a class that derives from
+ * mozilla::SupportsWeakPtr.
+ * * WEAK_NON_INTRUSIVE is a pointer to a class that does not have any
+ * internal support for weak pointers, but does supply a
+ * OnWeakNonIntrusiveDetach method.
+ */
+enum class NativePtrType : size_t {
+ OWNING = 1,
+ WEAK_INTRUSIVE = 2,
+ WEAK_NON_INTRUSIVE = 3,
+ REFPTR = 4,
+};
+
+/**
+ * NativePtrPicker uses some C++ SFINAE template-fu to figure out what type of
+ * pointer the class specified by Impl needs to be.
+ *
+ * It does this by supplying multiple overloads of a method named Test.
+ * Various overloads are enabled or disabled depending on whether or not Impl
+ * can possibly support them.
+ *
+ * Each overload "returns" a reference to an array whose size corresponds to the
+ * value of each enum in NativePtrInternalType. That size is then converted back
+ * to the enum value, yielding the right type.
+ */
+template <class Impl>
+class NativePtrPicker {
+ // Just shorthand for each overload's return type
+ template <NativePtrType PtrType>
+ using ResultTypeT = char (&)[static_cast<size_t>(PtrType)];
+
+ // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE
+ template <typename I>
+ static auto Test(void*)
+ -> std::enable_if_t<std::is_base_of<SupportsWeakPtr, I>::value,
+ ResultTypeT<NativePtrType::WEAK_INTRUSIVE>>;
+
+ // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type
+ // WEAK_NON_INTRUSIVE
+ template <typename I>
+ static auto Test(void*)
+ -> std::enable_if_t<HasWeakNonIntrusiveDetach<I>::value,
+ ResultTypeT<NativePtrType::WEAK_NON_INTRUSIVE>>;
+
+ // We want the WEAK_NON_INTRUSIVE overload to take precedence over this one,
+ // so we only enable this overload if Impl is refcounted AND it does not
+ // implement OnWeakNonIntrusiveDetach. Yields type REFPTR.
+ template <typename I>
+ static auto Test(void*) -> std::enable_if_t<
+ std::conjunction_v<IsRefCounted<I>,
+ std::negation<HasWeakNonIntrusiveDetach<I>>>,
+ ResultTypeT<NativePtrType::REFPTR>>;
+
+ // This overload uses '...' as its param to make its arguments less specific;
+ // the compiler prefers more-specific overloads to less-specific ones.
+ // OWNING is the fallback type.
+ template <typename>
+ static char (&Test(...))[static_cast<size_t>(NativePtrType::OWNING)];
+
+ public:
+ // Given a hypothetical function call Test<Impl>, convert the size of its
+ // resulting array back into a NativePtrType enum value.
+ static const NativePtrType value =
+ static_cast<NativePtrType>(sizeof(Test<Impl>(nullptr)));
+};
+
+template <class Impl>
+inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) {
+ if (!handle) {
+ if (!env->ExceptionCheck()) {
+ ThrowException(env, "java/lang/NullPointerException",
+ NullHandle<Impl>().str);
+ }
+ return 0;
+ }
+ return handle;
+}
+
+/**
+ * This struct is used to describe various traits of a native pointer of type
+ * Impl that will be attached to a JNIObject.
+ *
+ * See the definition of the NativePtrType::OWNING specialization for comments
+ * describing the required fields.
+ */
+template <class Impl, NativePtrType Type = NativePtrPicker<Impl>::value>
+struct NativePtrTraits;
+
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::OWNING> {
+ using AccessorType =
+ Impl*; // Pointer-like type returned by Access() (an actual pointer in
+ // this case, but this is not strictly necessary)
+ using HandleType = Impl*; // Type of the pointer stored in JNIObject.mHandle
+ using RefType = Impl*; // Type of the pointer returned by Get()
+
+ /**
+ * Returns a RefType to the native implementation belonging to
+ * the given Java object.
+ */
+ static RefType Get(JNIEnv* env, jobject instance) {
+ static_assert(
+ std::is_same<HandleType, RefType>::value,
+ "HandleType and RefType must be identical for owning pointers");
+ return reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ }
+
+ /**
+ * Returns a RefType to the native implementation belonging to
+ * the given Java object.
+ */
+ template <class LocalRef>
+ static RefType Get(const LocalRef& instance) {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ /**
+ * Given a RefType, returns the pointer-like AccessorType used for
+ * manipulating the native object.
+ */
+ static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) {
+ static_assert(
+ std::is_same<AccessorType, RefType>::value,
+ "AccessorType and RefType must be identical for owning pointers");
+ return aImpl;
+ }
+
+ /**
+ * Set the JNIObject's handle to the provided pointer, clearing any previous
+ * handle if necessary.
+ */
+ template <class LocalRef>
+ static void Set(const LocalRef& instance, UniquePtr<Impl>&& ptr) {
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(),
+ reinterpret_cast<uintptr_t>(ptr.release()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ /**
+ * Clear the JNIObject's handle.
+ */
+ template <class LocalRef>
+ static void Clear(const LocalRef& instance) {
+ UniquePtr<Impl> ptr(reinterpret_cast<RefType>(
+ GetNativeHandle(instance.Env(), instance.Get())));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (ptr) {
+ SetNativeHandle(instance.Env(), instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+ }
+};
+
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_INTRUSIVE> {
+ using AccessorType = Impl*;
+ using HandleType = WeakPtr<Impl>*;
+ using RefType = WeakPtr<Impl>;
+
+ static RefType Get(JNIEnv* env, jobject instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ return *ptr;
+ }
+
+ template <class LocalRef>
+ static RefType Get(const LocalRef& instance) {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ static AccessorType Access(RefType aPtr, JNIEnv* aEnv = nullptr) {
+ AccessorType const impl = *aPtr;
+ if (!impl) {
+ JNIEnv* env = aEnv ? aEnv : mozilla::jni::GetEnvForThread();
+ ThrowException(env, "java/lang/NullPointerException",
+ NullWeakPtr<Impl>().str);
+ }
+
+ return impl;
+ }
+
+ template <class LocalRef>
+ static void Set(const LocalRef& instance, Impl* ptr) {
+ // Create the new handle first before clearing any old handle, so the
+ // new handle is guaranteed to have different value than any old handle.
+ const uintptr_t handle =
+ reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr));
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(), handle);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template <class LocalRef>
+ static void Clear(const LocalRef& instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ GetNativeHandle(instance.Env(), instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (ptr) {
+ SetNativeHandle(instance.Env(), instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ delete ptr;
+ }
+ }
+};
+
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::REFPTR> {
+ using AccessorType = Impl*;
+ using HandleType = RefPtr<Impl>*;
+ using RefType = Impl*;
+
+ static RefType Get(JNIEnv* env, jobject instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ if (!ptr) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(*ptr);
+ return *ptr;
+ }
+
+ template <class LocalRef>
+ static RefType Get(const LocalRef& instance) {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) {
+ static_assert(std::is_same<AccessorType, RefType>::value,
+ "AccessorType and RefType must be identical for refpointers");
+ return aImpl;
+ }
+
+ template <class LocalRef>
+ static void Set(const LocalRef& instance, RefType ptr) {
+ // Create the new handle first before clearing any old handle, so the
+ // new handle is guaranteed to have different value than any old handle.
+ const uintptr_t handle = reinterpret_cast<uintptr_t>(new RefPtr<Impl>(ptr));
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(), handle);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template <class LocalRef>
+ static void Clear(const LocalRef& instance) {
+ const auto ptr = reinterpret_cast<HandleType>(
+ GetNativeHandle(instance.Env(), instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (ptr) {
+ SetNativeHandle(instance.Env(), instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ delete ptr;
+ }
+ }
+};
+
+} // namespace detail
+
+// Forward declarations
+template <typename NativeImpl>
+class NativeWeakPtr;
+template <typename NativeImpl>
+class NativeWeakPtrHolder;
+
+namespace detail {
+
+/**
+ * Given the class of a native implementation, as well as its
+ * NativePtrInternalType, resolve traits for that type that will be used by
+ * the NativeWeakPtrControlBlock.
+ *
+ * Note that we only implement specializations for OWNING and REFPTR types,
+ * as a WEAK_INTRUSIVE type should not be using NativeWeakPtr anyway. The build
+ * will fail if such an attempt is made.
+ *
+ * Traits need to implement two things:
+ * 1. A |Type| field that resolves to a pointer type to be stored in the
+ * JNIObject's handle. It is assumed that setting a |Type| object to nullptr
+ * is sufficient to delete the underlying object.
+ * 2. A static |AsRaw| method that converts a pointer of |Type| into a raw
+ * pointer.
+ */
+template <
+ typename NativeImpl,
+ NativePtrInternalType PtrType =
+ ::mozilla::jni::detail::NativePtrInternalPicker<NativeImpl>::value>
+struct NativeWeakPtrControlBlockStorageTraits;
+
+template <typename NativeImpl>
+struct NativeWeakPtrControlBlockStorageTraits<
+ NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> {
+ using Type = UniquePtr<NativeImpl>;
+
+ static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); }
+};
+
+template <typename NativeImpl>
+struct NativeWeakPtrControlBlockStorageTraits<
+ NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> {
+ using Type = RefPtr<NativeImpl>;
+
+ static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); }
+};
+
+// Forward Declaration
+template <typename NativeImpl>
+class Accessor;
+
+/**
+ * This class contains the shared data that is referenced by all NativeWeakPtr
+ * objects that reference the same object.
+ *
+ * It retains a WeakRef to the Java object that owns this native object.
+ * It uses a RWLock to control access to the native pointer itself.
+ * Read locks are used when accessing the pointer (even when calling non-const
+ * methods on the native object).
+ * A write lock is only used when it is time to destroy the native object and
+ * we need to clear the value of mNativeImpl.
+ */
+template <typename NativeImpl>
+class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final {
+ public:
+ using StorageTraits = NativeWeakPtrControlBlockStorageTraits<NativeImpl>;
+ using StorageType = typename StorageTraits::Type;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeWeakPtrControlBlock)
+
+ NativeWeakPtrControlBlock(const NativeWeakPtrControlBlock&) = delete;
+ NativeWeakPtrControlBlock(NativeWeakPtrControlBlock&&) = delete;
+ NativeWeakPtrControlBlock& operator=(const NativeWeakPtrControlBlock&) =
+ delete;
+ NativeWeakPtrControlBlock& operator=(NativeWeakPtrControlBlock&&) = delete;
+
+ // This is safe to call on any thread because mJavaOwner is immutable.
+ mozilla::jni::Object::WeakRef GetJavaOwner() const { return mJavaOwner; }
+
+ private:
+ NativeWeakPtrControlBlock(::mozilla::jni::Object::Param aJavaOwner,
+ StorageType&& aNativeImpl)
+ : mJavaOwner(aJavaOwner),
+ mLock("mozilla::jni::detail::NativeWeakPtrControlBlock"),
+ mNativeImpl(std::move(aNativeImpl)) {}
+
+ ~NativeWeakPtrControlBlock() {
+ // Make sure that somebody, somewhere, has detached us before destroying.
+ MOZ_ASSERT(!(*this));
+ }
+
+ /**
+ * Clear the native pointer so that subsequent accesses to the native pointer
+ * via this control block are no longer available.
+ *
+ * We return the native pointer to the caller so that it may proceed with
+ * cleaning up its resources.
+ */
+ StorageType Clear() {
+ StorageType nativeImpl(nullptr);
+
+ { // Scope for lock
+ AutoWriteLock lock(mLock);
+ std::swap(mNativeImpl, nativeImpl);
+ }
+
+ return nativeImpl;
+ }
+
+ MOZ_PUSH_IGNORE_THREAD_SAFETY
+ void Lock() const { mLock.ReadLock(); }
+
+ void Unlock() const { mLock.ReadUnlock(); }
+ MOZ_POP_THREAD_SAFETY
+
+#if defined(DEBUG)
+ // This is kind of expensive, so we only support it in debug builds.
+ explicit operator bool() const {
+ AutoReadLock lock(mLock);
+ return !!mNativeImpl;
+ }
+#endif // defined(DEBUG)
+
+ private:
+ friend class Accessor<NativeImpl>;
+ friend class NativeWeakPtr<NativeImpl>;
+ friend class NativeWeakPtrHolder<NativeImpl>;
+
+ private:
+ const mozilla::jni::Object::WeakRef mJavaOwner;
+ mutable RWLock mLock MOZ_UNANNOTATED; // Protects mNativeImpl
+ StorageType mNativeImpl;
+};
+
+/**
+ * If you want to temporarily access the object held by a NativeWeakPtr, you
+ * must obtain one of these Accessor objects from the pointer. Access must
+ * be done _exclusively_ using once of these objects!
+ */
+template <typename NativeImpl>
+class MOZ_STACK_CLASS Accessor final {
+ public:
+ ~Accessor() {
+ if (mCtlBlock) {
+ mCtlBlock->Unlock();
+ }
+ }
+
+ // Check whether the object is still valid before doing anything else
+ explicit operator bool() const { return mCtlBlock && mCtlBlock->mNativeImpl; }
+
+ // Normal member access
+ NativeImpl* operator->() const {
+ return NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw(
+ mCtlBlock->mNativeImpl);
+ }
+
+ // This allows us to support calling a pointer to a member function
+ template <typename Member>
+ auto operator->*(Member aMember) const {
+ NativeImpl* impl =
+ NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw(
+ mCtlBlock->mNativeImpl);
+ return [impl, member = aMember](auto&&... aArgs) {
+ return (impl->*member)(std::forward<decltype(aArgs)>(aArgs)...);
+ };
+ }
+
+ // Only available for NativeImpl types that actually use refcounting.
+ // The idea here is that it should be possible to obtain a strong ref from
+ // a NativeWeakPtr if and only if NativeImpl supports refcounting.
+ template <typename I = NativeImpl>
+ auto AsRefPtr() const -> std::enable_if_t<IsRefCounted<I>::value, RefPtr<I>> {
+ MOZ_ASSERT(I::HasThreadSafeRefCnt::value || NS_IsMainThread());
+ return mCtlBlock->mNativeImpl;
+ }
+
+ Accessor(const Accessor&) = delete;
+ Accessor(Accessor&&) = delete;
+ Accessor& operator=(const Accessor&) = delete;
+ Accessor& operator=(Accessor&&) = delete;
+
+ private:
+ explicit Accessor(
+ const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock)
+ : mCtlBlock(aCtlBlock) {
+ if (aCtlBlock) {
+ aCtlBlock->Lock();
+ }
+ }
+
+ private:
+ friend class NativeWeakPtr<NativeImpl>;
+ friend class NativeWeakPtrHolder<NativeImpl>;
+
+ private:
+ const RefPtr<NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+};
+
+} // namespace detail
+
+using DetachPromise = mozilla::MozPromise<bool, nsresult, true>;
+
+/**
+ * This class implements support for thread-safe weak pointers to native objects
+ * that are owned by Java objects deriving from JNIObject.
+ *
+ * Any code that wants to access such a native object must have a copy of
+ * a NativeWeakPtr to that object.
+ */
+template <typename NativeImpl>
+class NativeWeakPtr {
+ public:
+ using Accessor = detail::Accessor<NativeImpl>;
+
+ /**
+ * Call this method to access the underlying object referenced by this
+ * NativeWeakPtr.
+ *
+ * Always check the returned Accessor object for availability before calling
+ * methods on it.
+ *
+ * For example, given:
+ *
+ * NativeWeakPtr<Foo> foo;
+ * auto accessor = foo.Access();
+ * if (accessor) {
+ * // Okay, safe to work with
+ * accessor->DoStuff();
+ * } else {
+ * // The object's strong reference was cleared and is no longer available!
+ * }
+ */
+ Accessor Access() const { return Accessor(mCtlBlock); }
+
+ /**
+ * Detach the underlying object's strong reference from its owning Java object
+ * and clean it up.
+ */
+ RefPtr<DetachPromise> Detach();
+
+ /**
+ * This method does not indicate whether or not the weak pointer is still
+ * valid; it only indicates whether we're actually attached to one.
+ */
+ bool IsAttached() const { return !!mCtlBlock; }
+
+ /**
+ * Does this pointer reference the same object as the one referenced by the
+ * provided Accessor?
+ */
+ bool IsSame(const Accessor& aAccessor) const {
+ return mCtlBlock == aAccessor.mCtlBlock;
+ }
+
+ /**
+ * Does this pointer reference the same object as the one referenced by the
+ * provided Control Block?
+ */
+ bool IsSame(const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>&
+ aOther) const {
+ return mCtlBlock == aOther;
+ }
+
+ NativeWeakPtr() = default;
+ MOZ_IMPLICIT NativeWeakPtr(decltype(nullptr)) {}
+ NativeWeakPtr(const NativeWeakPtr& aOther) = default;
+ NativeWeakPtr(NativeWeakPtr&& aOther) = default;
+ NativeWeakPtr& operator=(const NativeWeakPtr& aOther) = default;
+ NativeWeakPtr& operator=(NativeWeakPtr&& aOther) = default;
+
+ NativeWeakPtr& operator=(decltype(nullptr)) {
+ mCtlBlock = nullptr;
+ return *this;
+ }
+
+ protected:
+ // Construction of initial NativeWeakPtr for aCtlBlock
+ explicit NativeWeakPtr(
+ already_AddRefed<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock)
+ : mCtlBlock(aCtlBlock) {}
+
+ private:
+ // Construction of subsequent NativeWeakPtrs for aCtlBlock
+ explicit NativeWeakPtr(
+ const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock)
+ : mCtlBlock(aCtlBlock) {}
+
+ friend class NativeWeakPtrHolder<NativeImpl>;
+
+ protected:
+ RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+};
+
+/**
+ * A pointer to an instance of this class should be stored in a Java object's
+ * JNIObject handle. New instances of native objects wrapped by NativeWeakPtr
+ * are created using the static methods of this class.
+ *
+ * Why do we have distinct methods here instead of using AttachNative like other
+ * pointer types that may be stored in JNIObject?
+ *
+ * Essentially, we want the creation and use of NativeWeakPtr to be as
+ * deliberate as possible. Forcing a different creation mechanism is part of
+ * that emphasis.
+ *
+ * Example:
+ *
+ * class NativeFoo {
+ * public:
+ * NativeFoo();
+ * void Bar();
+ * // The following method is required to be used with NativeWeakPtr
+ * void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer);
+ * };
+ *
+ * java::Object::LocalRef javaObj(...);
+ *
+ * // Create a new Foo that is attached to javaObj
+ * auto weakFoo = NativeWeakPtrHolder<NativeFoo>::Attach(javaObj);
+ *
+ * // Now I can save weakFoo, access it, do whatever I want
+ * if (auto accWeakFoo = weakFoo.Access()) {
+ * accWeakFoo->Bar();
+ * }
+ *
+ * // Detach from javaObj and clean up
+ * weakFoo.Detach();
+ */
+template <typename NativeImpl>
+class MOZ_HEAP_CLASS NativeWeakPtrHolder final
+ : public NativeWeakPtr<NativeImpl> {
+ using Base = NativeWeakPtr<NativeImpl>;
+
+ public:
+ using Accessor = typename Base::Accessor;
+ using StorageTraits =
+ typename detail::NativeWeakPtrControlBlock<NativeImpl>::StorageTraits;
+ using StorageType = typename StorageTraits::Type;
+
+ /**
+ * Create a new NativeImpl object, wrap it in a NativeWeakPtr, and store it
+ * in the Java object's JNIObject handle.
+ *
+ * @return A NativeWeakPtr object that references the newly-attached object.
+ */
+ template <typename Cls, typename JNIType, typename... Args>
+ static NativeWeakPtr<NativeImpl> Attach(const Ref<Cls, JNIType>& aJavaObject,
+ Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ StorageType nativeImpl(new NativeImpl(std::forward<Args>(aArgs)...));
+ return AttachInternal(aJavaObject, std::move(nativeImpl));
+ }
+
+ /**
+ * Given a new NativeImpl object, wrap it in a NativeWeakPtr, and store it
+ * in the Java object's JNIObject handle.
+ *
+ * @return A NativeWeakPtr object that references the newly-attached object.
+ */
+ template <typename Cls, typename JNIType>
+ static NativeWeakPtr<NativeImpl> AttachExisting(
+ const Ref<Cls, JNIType>& aJavaObject,
+ already_AddRefed<NativeImpl> aNativeImpl) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ StorageType nativeImpl(aNativeImpl);
+ return AttachInternal(aJavaObject, std::move(nativeImpl));
+ }
+
+ ~NativeWeakPtrHolder() = default;
+
+ MOZ_IMPLICIT NativeWeakPtrHolder(decltype(nullptr)) = delete;
+ NativeWeakPtrHolder(const NativeWeakPtrHolder&) = delete;
+ NativeWeakPtrHolder(NativeWeakPtrHolder&&) = delete;
+ NativeWeakPtrHolder& operator=(const NativeWeakPtrHolder&) = delete;
+ NativeWeakPtrHolder& operator=(NativeWeakPtrHolder&&) = delete;
+ NativeWeakPtrHolder& operator=(decltype(nullptr)) = delete;
+
+ private:
+ template <typename Cls>
+ NativeWeakPtrHolder(const LocalRef<Cls>& aJavaObject,
+ StorageType&& aNativeImpl)
+ : NativeWeakPtr<NativeImpl>(
+ do_AddRef(new NativeWeakPtrControlBlock<NativeImpl>(
+ aJavaObject, std::move(aNativeImpl)))) {}
+
+ /**
+ * Internal function that actually wraps the native pointer, binds it to the
+ * JNIObject, and then returns the NativeWeakPtr result.
+ */
+ template <typename Cls, typename JNIType>
+ static NativeWeakPtr<NativeImpl> AttachInternal(
+ const Ref<Cls, JNIType>& aJavaObject, StorageType&& aPtr) {
+ auto localJavaObject = ToLocalRef(aJavaObject);
+ NativeWeakPtrHolder<NativeImpl>* holder =
+ new NativeWeakPtrHolder<NativeImpl>(localJavaObject, std::move(aPtr));
+ static_assert(
+ NativePtrPicker<NativeImpl>::value == NativePtrType::WEAK_NON_INTRUSIVE,
+ "This type is not compatible with mozilla::jni::NativeWeakPtr");
+ NativePtrTraits<NativeImpl>::Set(localJavaObject, holder);
+ return NativeWeakPtr<NativeImpl>(holder->mCtlBlock);
+ }
+};
+
+namespace detail {
+
+/**
+ * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type.
+ */
+template <class Impl>
+struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_NON_INTRUSIVE> {
+ using AccessorType = typename NativeWeakPtrHolder<Impl>::Accessor;
+ using HandleType = NativeWeakPtrHolder<Impl>*;
+ using RefType = NativeWeakPtrHolder<Impl>* const;
+
+ static RefType Get(JNIEnv* env, jobject instance) {
+ return GetHandle(env, instance);
+ }
+
+ template <typename Cls>
+ static RefType Get(const LocalRef<Cls>& instance) {
+ return GetHandle(instance.Env(), instance.Get());
+ }
+
+ static AccessorType Access(RefType aPtr) { return aPtr->Access(); }
+
+ template <typename Cls>
+ static void Set(const LocalRef<Cls>& instance, HandleType ptr) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ const uintptr_t handle = reinterpret_cast<uintptr_t>(ptr);
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(), handle);
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template <typename Cls>
+ static void Clear(const LocalRef<Cls>& instance) {
+ auto ptr = reinterpret_cast<HandleType>(
+ GetNativeHandle(instance.Env(), instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+
+ if (!ptr) {
+ return;
+ }
+
+ ptr->Detach();
+ }
+
+ // This call is not safe to do unless we know for sure that instance's
+ // native handle has not changed. It is up to NativeWeakPtrDetachRunnable
+ // to perform this check.
+ template <typename Cls>
+ static void ClearFinish(const LocalRef<Cls>& instance) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ JNIEnv* const env = instance.Env();
+ auto ptr =
+ reinterpret_cast<HandleType>(GetNativeHandle(env, instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ MOZ_RELEASE_ASSERT(!!ptr);
+
+ SetNativeHandle(env, instance.Get(), 0);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ // Deletion of ptr is done by the caller
+ }
+
+ // The call is stale if the native object has been destroyed on the
+ // Gecko side, but the Java object is still attached to it through
+ // a weak pointer. Stale calls should be discarded. Note that it's
+ // an error if holder is nullptr here; we return false but the
+ // native call will throw an error.
+ template <class LocalRef>
+ static bool IsStale(const LocalRef& instance) {
+ JNIEnv* const env = mozilla::jni::GetEnvForThread();
+
+ // We cannot use Get here because that method throws an exception when the
+ // object is null, which is a valid state for a stale call.
+ const auto holder =
+ reinterpret_cast<HandleType>(GetNativeHandle(env, instance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+
+ if (!holder || !holder->IsAttached()) {
+ return true;
+ }
+
+ auto acc(holder->Access());
+ return !acc;
+ }
+
+ private:
+ static HandleType GetHandle(JNIEnv* env, jobject instance) {
+ return reinterpret_cast<HandleType>(
+ CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
+ }
+
+ template <typename Cls>
+ static HandleType GetHandle(const LocalRef<Cls>& instance) {
+ return GetHandle(instance.Env(), instance.Get());
+ }
+
+ friend class NativeWeakPtrHolder<Impl>;
+};
+
+} // namespace detail
+
+using namespace detail;
+
+/**
+ * For JNI native methods that are dispatched to a proxy, i.e. using
+ * @WrapForJNI(dispatchTo = "proxy"), the implementing C++ class must provide a
+ * OnNativeCall member. Subsequently, every native call is automatically
+ * wrapped in a functor object, and the object is passed to OnNativeCall. The
+ * OnNativeCall implementation can choose to invoke the call, save it, dispatch
+ * it to a different thread, etc. Each copy of functor may only be invoked
+ * once.
+ *
+ * class MyClass : public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * template<class Functor>
+ * class ProxyRunnable final : public Runnable
+ * {
+ * Functor mCall;
+ * public:
+ * ProxyRunnable(Functor&& call) : mCall(std::move(call)) {}
+ * virtual void run() override { mCall(); }
+ * };
+ *
+ * public:
+ * template<class Functor>
+ * static void OnNativeCall(Functor&& call)
+ * {
+ * RunOnAnotherThread(new ProxyRunnable(std::move(call)));
+ * }
+ * };
+ */
+
+namespace detail {
+
+// ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied
+// call may happen outside of the original JNI native call, we must save all
+// JNI ref arguments as global refs to avoid the arguments going out of scope.
+template <typename T>
+struct ProxyArg {
+ static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>,
+ "T must be primitive type");
+
+ // Primitive types can be saved by value.
+ typedef T Type;
+ typedef typename TypeAdapter<T>::JNIType JNIType;
+
+ static void Clear(JNIEnv* env, Type&) {}
+
+ static Type From(JNIEnv* env, JNIType val) {
+ return TypeAdapter<T>::ToNative(env, val);
+ }
+};
+
+template <class C, typename T>
+struct ProxyArg<Ref<C, T>> {
+ // Ref types need to be saved by global ref.
+ typedef typename C::GlobalRef Type;
+ typedef typename TypeAdapter<Ref<C, T>>::JNIType JNIType;
+
+ static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); }
+
+ static Type From(JNIEnv* env, JNIType val) {
+ return Type(env, C::Ref::From(val));
+ }
+};
+
+template <typename C>
+struct ProxyArg<const C&> : ProxyArg<C> {};
+template <>
+struct ProxyArg<StringParam> : ProxyArg<String::Ref> {};
+template <class C>
+struct ProxyArg<LocalRef<C>> : ProxyArg<typename C::Ref> {};
+
+// ProxyNativeCall implements the functor object that is passed to OnNativeCall
+template <class Impl, class Owner, bool IsStatic,
+ bool HasThisArg /* has instance/class local ref in the call */,
+ typename... Args>
+class ProxyNativeCall {
+ // "this arg" refers to the Class::LocalRef (for static methods) or
+ // Owner::LocalRef (for instance methods) that we optionally (as indicated
+ // by HasThisArg) pass into the destination C++ function.
+ using ThisArgClass = std::conditional_t<IsStatic, Class, Owner>;
+ using ThisArgJNIType = std::conditional_t<IsStatic, jclass, jobject>;
+
+ // Type signature of the destination C++ function, which matches the
+ // Method template parameter in NativeStubImpl::Wrap.
+ using NativeCallType = std::conditional_t<
+ IsStatic,
+ std::conditional_t<HasThisArg, void (*)(const Class::LocalRef&, Args...),
+ void (*)(Args...)>,
+ std::conditional_t<
+ HasThisArg, void (Impl::*)(const typename Owner::LocalRef&, Args...),
+ void (Impl::*)(Args...)>>;
+
+ // Destination C++ function.
+ NativeCallType mNativeCall;
+ // Saved this arg.
+ typename ThisArgClass::GlobalRef mThisArg;
+ // Saved arguments.
+ std::tuple<typename ProxyArg<Args>::Type...> mArgs;
+
+ // We cannot use IsStatic and HasThisArg directly (without going through
+ // extra hoops) because GCC complains about invalid overloads, so we use
+ // another pair of template parameters, Static and ThisArg.
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<Static && ThisArg, void> Call(
+ const Class::LocalRef& cls, std::index_sequence<Indices...>) const {
+ (*mNativeCall)(cls, std::get<Indices>(mArgs)...);
+ }
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<Static && !ThisArg, void> Call(
+ const Class::LocalRef& cls, std::index_sequence<Indices...>) const {
+ (*mNativeCall)(std::get<Indices>(mArgs)...);
+ }
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<!Static && ThisArg, void> Call(
+ const typename Owner::LocalRef& inst,
+ std::index_sequence<Indices...>) const {
+ auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst));
+ MOZ_CATCH_JNI_EXCEPTION(inst.Env());
+ (impl->*mNativeCall)(inst, std::get<Indices>(mArgs)...);
+ }
+
+ template <bool Static, bool ThisArg, size_t... Indices>
+ std::enable_if_t<!Static && !ThisArg, void> Call(
+ const typename Owner::LocalRef& inst,
+ std::index_sequence<Indices...>) const {
+ auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst));
+ MOZ_CATCH_JNI_EXCEPTION(inst.Env());
+ (impl->*mNativeCall)(std::get<Indices>(mArgs)...);
+ }
+
+ template <size_t... Indices>
+ void Clear(JNIEnv* env, std::index_sequence<Indices...>) {
+ int dummy[] = {
+ (ProxyArg<Args>::Clear(env, std::get<Indices>(mArgs)), 0)...};
+ mozilla::Unused << dummy;
+ }
+
+ static decltype(auto) GetNativeObject(Class::Param thisArg) {
+ return nullptr;
+ }
+
+ static decltype(auto) GetNativeObject(typename Owner::Param thisArg) {
+ return NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(GetEnvForThread(), thisArg.Get()));
+ }
+
+ public:
+ // The class that implements the call target.
+ typedef Impl TargetClass;
+ typedef typename ThisArgClass::Param ThisArgType;
+
+ static const bool isStatic = IsStatic;
+
+ ProxyNativeCall(ThisArgJNIType thisArg, NativeCallType nativeCall,
+ JNIEnv* env, typename ProxyArg<Args>::JNIType... args)
+ : mNativeCall(nativeCall),
+ mThisArg(env, ThisArgClass::Ref::From(thisArg)),
+ mArgs(ProxyArg<Args>::From(env, args)...) {}
+
+ ProxyNativeCall(ProxyNativeCall&&) = default;
+ ProxyNativeCall(const ProxyNativeCall&) = default;
+
+ // Get class ref for static calls or object ref for instance calls.
+ typename ThisArgClass::Param GetThisArg() const { return mThisArg; }
+
+ // Get the native object targeted by this call.
+ // Returns nullptr for static calls.
+ decltype(auto) GetNativeObject() const { return GetNativeObject(mThisArg); }
+
+ // Return if target is the given function pointer / pointer-to-member.
+ // Because we can only compare pointers of the same type, we use a
+ // templated overload that is chosen only if given a different type of
+ // pointer than our target pointer type.
+ bool IsTarget(NativeCallType call) const { return call == mNativeCall; }
+ template <typename T>
+ bool IsTarget(T&&) const {
+ return false;
+ }
+
+ // Redirect the call to another function / class member with the same
+ // signature as the original target. Crash if given a wrong signature.
+ void SetTarget(NativeCallType call) { mNativeCall = call; }
+ template <typename T>
+ void SetTarget(T&&) const {
+ MOZ_CRASH();
+ }
+
+ void operator()() {
+ JNIEnv* const env = GetEnvForThread();
+ typename ThisArgClass::LocalRef thisArg(env, mThisArg);
+ Call<IsStatic, HasThisArg>(thisArg, std::index_sequence_for<Args...>{});
+
+ // Clear all saved global refs. We do this after the call is invoked,
+ // and not inside the destructor because we already have a JNIEnv here,
+ // so it's more efficient to clear out the saved args here. The
+ // downside is that the call can only be invoked once.
+ Clear(env, std::index_sequence_for<Args...>{});
+ mThisArg.Clear(env);
+ }
+};
+
+template <class Impl, bool HasThisArg, typename... Args>
+struct Dispatcher {
+ template <class Traits, bool IsStatic = Traits::isStatic,
+ typename... ProxyArgs>
+ static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::PROXY, void>
+ Run(ProxyArgs&&... args) {
+ Impl::OnNativeCall(
+ ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>(std::forward<ProxyArgs>(args)...));
+ }
+
+ template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg,
+ typename... ProxyArgs>
+ static std::enable_if_t<
+ Traits::dispatchTarget == DispatchTarget::GECKO_PRIORITY, void>
+ Run(ThisArg thisArg, ProxyArgs&&... args) {
+ // For a static method, do not forward the "this arg" (i.e. the class
+ // local ref) if the implementation does not request it. This saves us
+ // a pair of calls to add/delete global ref.
+ auto proxy =
+ ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr,
+ std::forward<ProxyArgs>(args)...);
+ DispatchToGeckoPriorityQueue(
+ NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy)));
+ }
+
+ template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg,
+ typename... ProxyArgs>
+ static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::GECKO, void>
+ Run(ThisArg thisArg, ProxyArgs&&... args) {
+ // For a static method, do not forward the "this arg" (i.e. the class
+ // local ref) if the implementation does not request it. This saves us
+ // a pair of calls to add/delete global ref.
+ auto proxy =
+ ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr,
+ std::forward<ProxyArgs>(args)...);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy)));
+ }
+
+ template <class Traits, bool IsStatic = false, typename... ProxyArgs>
+ static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::CURRENT,
+ void>
+ Run(ProxyArgs&&... args) {
+ MOZ_CRASH("Unreachable code");
+ }
+};
+
+} // namespace detail
+
+// Wrapper methods that convert arguments from the JNI types to the native
+// types, e.g. from jobject to jni::Object::Ref. For instance methods, the
+// wrapper methods also convert calls to calls on objects.
+//
+// We need specialization for static/non-static because the two have different
+// signatures (jobject vs jclass and Impl::*Method vs *Method).
+// We need specialization for return type, because void return type requires
+// us to not deal with the return value.
+
+// Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry
+#ifdef __i386__
+# define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer))
+#else
+# define MOZ_JNICALL JNICALL
+#endif
+
+template <class Traits, class Impl, class Args = typename Traits::Args>
+class NativeStub;
+
+template <class Traits, class Impl, typename... Args>
+class NativeStub<Traits, Impl, jni::Args<Args...>> {
+ using Owner = typename Traits::Owner;
+ using ReturnType = typename Traits::ReturnType;
+
+ static constexpr bool isStatic = Traits::isStatic;
+ static constexpr bool isVoid = std::is_void_v<ReturnType>;
+
+ struct VoidType {
+ using JNIType = void;
+ };
+ using ReturnJNIType =
+ typename std::conditional_t<isVoid, VoidType,
+ TypeAdapter<ReturnType>>::JNIType;
+
+ using ReturnTypeForNonVoidInstance =
+ std::conditional_t<!isStatic && !isVoid, ReturnType, VoidType>;
+ using ReturnTypeForVoidInstance =
+ std::conditional_t<!isStatic && isVoid, ReturnType, VoidType&>;
+ using ReturnTypeForNonVoidStatic =
+ std::conditional_t<isStatic && !isVoid, ReturnType, VoidType>;
+ using ReturnTypeForVoidStatic =
+ std::conditional_t<isStatic && isVoid, ReturnType, VoidType&>;
+
+ static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid,
+ "Dispatched calls must have void return type");
+
+ public:
+ // Non-void instance method
+ template <ReturnTypeForNonVoidInstance (Impl::*Method)(Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return ReturnJNIType();
+ }
+ return TypeAdapter<ReturnType>::FromNative(
+ env, (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...));
+ }
+
+ // Non-void instance method with instance reference
+ template <ReturnTypeForNonVoidInstance (Impl::*Method)(
+ const typename Owner::LocalRef&, Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return ReturnJNIType();
+ }
+ auto self = Owner::LocalRef::Adopt(env, instance);
+ const auto res = TypeAdapter<ReturnType>::FromNative(
+ env, (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...));
+ self.Forget();
+ return res;
+ }
+
+ // Void instance method
+ template <ReturnTypeForVoidInstance (Impl::*Method)(Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>(
+ instance, Method, env, args...);
+ return;
+ }
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return;
+ }
+ (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...);
+ }
+
+ // Void instance method with instance reference
+ template <ReturnTypeForVoidInstance (Impl::*Method)(
+ const typename Owner::LocalRef&, Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>(
+ instance, Method, env, args...);
+ return;
+ }
+
+ auto impl = NativePtrTraits<Impl>::Access(
+ NativePtrTraits<Impl>::Get(env, instance));
+ if (!impl) {
+ // There is a pending JNI exception at this point.
+ return;
+ }
+ auto self = Owner::LocalRef::Adopt(env, instance);
+ (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...);
+ self.Forget();
+ }
+
+ // Overload for DisposeNative
+ template <ReturnTypeForVoidInstance (*DisposeNative)(
+ const typename Owner::LocalRef&)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ using LocalRef = typename Owner::LocalRef;
+ Dispatcher<Impl, /* HasThisArg */ false, const LocalRef&>::template Run<
+ Traits, /* IsStatic */ true>(
+ /* ThisArg */ nullptr, DisposeNative, env, instance);
+ return;
+ }
+
+ auto self = Owner::LocalRef::Adopt(env, instance);
+ DisposeNative(self);
+ self.Forget();
+ }
+
+ // Non-void static method
+ template <ReturnTypeForNonVoidStatic (*Method)(Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jclass, typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ return TypeAdapter<ReturnType>::FromNative(
+ env, (*Method)(TypeAdapter<Args>::ToNative(env, args)...));
+ }
+
+ // Non-void static method with class reference
+ template <ReturnTypeForNonVoidStatic (*Method)(const Class::LocalRef&,
+ Args...)>
+ static MOZ_JNICALL ReturnJNIType
+ Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ auto clazz = Class::LocalRef::Adopt(env, cls);
+ const auto res = TypeAdapter<ReturnType>::FromNative(
+ env, (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...));
+ clazz.Forget();
+ return res;
+ }
+
+ // Void static method
+ template <ReturnTypeForVoidStatic (*Method)(Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>(
+ cls, Method, env, args...);
+ return;
+ }
+
+ (*Method)(TypeAdapter<Args>::ToNative(env, args)...);
+ }
+
+ // Void static method with class reference
+ template <ReturnTypeForVoidStatic (*Method)(const Class::LocalRef&, Args...)>
+ static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls,
+ typename TypeAdapter<Args>::JNIType... args) {
+ MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
+
+ if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
+ Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>(
+ cls, Method, env, args...);
+ return;
+ }
+
+ auto clazz = Class::LocalRef::Adopt(env, cls);
+ (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...);
+ clazz.Forget();
+ }
+};
+
+// Generate a JNINativeMethod from a native
+// method's traits class and a wrapped stub.
+template <class Traits, typename Ret, typename... Args>
+constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*,
+ Args...)) {
+ return {Traits::name, Traits::signature, reinterpret_cast<void*>(stub)};
+}
+
+// Class inherited by implementing class.
+template <class Cls, class Impl>
+class NativeImpl {
+ typedef typename Cls::template Natives<Impl> Natives;
+
+ static bool sInited;
+
+ public:
+ static void Init() {
+ if (sInited) {
+ return;
+ }
+ const auto& ctx = typename Cls::Context();
+ ctx.Env()->RegisterNatives(
+ ctx.ClassRef(), Natives::methods,
+ sizeof(Natives::methods) / sizeof(Natives::methods[0]));
+ MOZ_CATCH_JNI_EXCEPTION(ctx.Env());
+ sInited = true;
+ }
+
+ protected:
+ // Associate a C++ instance with a Java instance.
+ static void AttachNative(const typename Cls::LocalRef& instance,
+ SupportsWeakPtr* ptr) {
+ static_assert(NativePtrPicker<Impl>::value == NativePtrType::WEAK_INTRUSIVE,
+ "Use another AttachNative for non-WeakPtr usage");
+ return NativePtrTraits<Impl>::Set(instance, static_cast<Impl*>(ptr));
+ }
+
+ static void AttachNative(const typename Cls::LocalRef& instance,
+ UniquePtr<Impl>&& ptr) {
+ static_assert(NativePtrPicker<Impl>::value == NativePtrType::OWNING,
+ "Use another AttachNative for WeakPtr or RefPtr usage");
+ return NativePtrTraits<Impl>::Set(instance, std::move(ptr));
+ }
+
+ static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) {
+ static_assert(NativePtrPicker<Impl>::value == NativePtrType::REFPTR,
+ "Use another AttachNative for non-RefPtr usage");
+ return NativePtrTraits<Impl>::Set(instance, ptr);
+ }
+
+ // Get the C++ instance associated with a Java instance.
+ // There is always a pending exception if the return value is nullptr.
+ static decltype(auto) GetNative(const typename Cls::LocalRef& instance) {
+ return NativePtrTraits<Impl>::Get(instance);
+ }
+
+ static void DisposeNative(const typename Cls::LocalRef& instance) {
+ NativePtrTraits<Impl>::Clear(instance);
+ }
+
+ NativeImpl() {
+ // Initialize on creation if not already initialized.
+ Init();
+ }
+};
+
+// Define static member.
+template <class C, class I>
+bool NativeImpl<C, I>::sInited;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Natives_h__
diff --git a/widget/android/jni/NativesInlines.h b/widget/android/jni/NativesInlines.h
new file mode 100644
index 0000000000..858b677715
--- /dev/null
+++ b/widget/android/jni/NativesInlines.h
@@ -0,0 +1,116 @@
+/* -*- 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 "Natives.h"
+
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::jni {
+
+namespace details {
+
+/**
+ * When a NativeWeakPtr is detached from its owning Java object, the calling
+ * thread invokes the implementation's OnWeakNonIntrusiveDetach to perform
+ * cleanup. We complete the remainder of the cleanup sequence on the Gecko
+ * main thread by expecting OnWeakNonIntrusiveDetach implementations to invoke
+ * this Runnable before exiting. It will move itself to the main thread if it
+ * is not already there.
+ */
+template <typename NativeImpl>
+class NativeWeakPtrDetachRunnable final : public Runnable {
+ public:
+ NativeWeakPtrDetachRunnable(
+ already_AddRefed<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock,
+ const Object::LocalRef& aOwner,
+ typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::Type
+ aNativeImpl)
+ : Runnable("mozilla::jni::detail::NativeWeakPtrDetachRunnable"),
+ mCtlBlock(aCtlBlock),
+ mOwner(aOwner),
+ mNativeImpl(std::move(aNativeImpl)),
+ mHasRun(false) {
+ MOZ_RELEASE_ASSERT(!!mCtlBlock);
+ MOZ_RELEASE_ASSERT(!!mNativeImpl);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(NativeWeakPtrDetachRunnable, Runnable)
+
+ NS_IMETHOD Run() override {
+ mHasRun = true;
+
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(this);
+ return NS_OK;
+ }
+
+ // Get the owner object's native implementation
+ auto owner = ToLocalRef(mOwner);
+ auto attachedNativeImpl = NativePtrTraits<NativeImpl>::Get(owner);
+ MOZ_RELEASE_ASSERT(!!attachedNativeImpl);
+
+ // NativePtrTraits::ClearFinish cleans out the JNIObject's handle, which
+ // obviously we don't want to attempt unless that handle still points to
+ // our native implementation.
+ if (attachedNativeImpl->IsSame(mCtlBlock)) {
+ NativePtrTraits<NativeImpl>::ClearFinish(owner);
+ }
+
+ // Now we destroy that native object.
+ mNativeImpl = nullptr;
+ mHolder.Resolve(true, __func__);
+ return NS_OK;
+ }
+
+ RefPtr<DetachPromise> GetPromise() { return mHolder.Ensure(__func__); }
+
+ private:
+ ~NativeWeakPtrDetachRunnable() {
+ // Guard against somebody forgetting to call this runnable.
+ MOZ_RELEASE_ASSERT(mHasRun, "You must run/dispatch this runnable!");
+ }
+
+ private:
+ RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+ Object::GlobalRef mOwner;
+ MozPromiseHolder<DetachPromise> mHolder;
+ typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::Type mNativeImpl;
+ bool mHasRun;
+};
+
+} // namespace details
+
+template <typename NativeImpl>
+RefPtr<DetachPromise> NativeWeakPtr<NativeImpl>::Detach() {
+ if (!IsAttached()) {
+ // Never attached to begin with; no-op
+ return DetachPromise::CreateAndResolve(true, __func__);
+ }
+
+ auto native = mCtlBlock->Clear();
+ if (!native) {
+ // Detach already in progress
+ return DetachPromise::CreateAndResolve(true, __func__);
+ }
+
+ Object::LocalRef owner(mCtlBlock->GetJavaOwner());
+ MOZ_RELEASE_ASSERT(!!owner);
+
+ // Save the raw pointer before we move native into the runnable so that we
+ // may call OnWeakNonIntrusiveDetach on it even after moving native into
+ // the runnable.
+ NativeImpl* rawImpl =
+ detail::NativeWeakPtrControlBlock<NativeImpl>::StorageTraits::AsRaw(
+ native);
+ RefPtr<details::NativeWeakPtrDetachRunnable<NativeImpl>> runnable =
+ new details::NativeWeakPtrDetachRunnable<NativeImpl>(
+ mCtlBlock.forget(), owner, std::move(native));
+ RefPtr<DetachPromise> promise = runnable->GetPromise();
+ rawImpl->OnWeakNonIntrusiveDetach(runnable.forget());
+ return promise;
+}
+
+} // namespace mozilla::jni
diff --git a/widget/android/jni/Refs.h b/widget/android/jni/Refs.h
new file mode 100644
index 0000000000..29812fe6a5
--- /dev/null
+++ b/widget/android/jni/Refs.h
@@ -0,0 +1,1135 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_Refs_h__
+#define mozilla_jni_Refs_h__
+
+#include <jni.h>
+
+#include <utility>
+
+#include "mozilla/fallible.h"
+#include "mozilla/jni/Utils.h"
+#include "mozilla/jni/TypeAdapter.h"
+#include "nsError.h" // for nsresult
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace jni {
+
+// Wrapped object reference (e.g. jobject, jclass, etc...)
+template <class Cls, typename JNIType>
+class Ref;
+// Represents a calling context for JNI methods.
+template <class Cls, typename JNIType>
+class Context;
+// Wrapped local reference that inherits from Ref.
+template <class Cls>
+class LocalRef;
+// Wrapped global reference that inherits from Ref.
+template <class Cls>
+class GlobalRef;
+// Wrapped weak reference that inherits from Ref.
+template <class Cls>
+class WeakRef;
+// Wrapped dangling reference that's owned by someone else.
+template <class Cls>
+class DependentRef;
+
+// Class to hold the native types of a method's arguments.
+// For example, if a method has signature (ILjava/lang/String;)V,
+// its arguments class would be jni::Args<int32_t, jni::String::Param>
+template <typename...>
+struct Args {};
+
+class Object;
+
+// Base class for Ref and its specializations.
+template <class Cls, typename Type>
+class Ref {
+ template <class C, typename T>
+ friend class Ref;
+
+ using Self = Ref<Cls, Type>;
+ using bool_type = void (Self::*)() const;
+ void non_null_reference() const {}
+
+ // A Cls-derivative that allows copying
+ // (e.g. when acting as a return value).
+ struct CopyableCtx : public Context<Cls, Type> {
+ CopyableCtx(JNIEnv* env, Type instance)
+ : Context<Cls, Type>(env, instance) {}
+
+ CopyableCtx(const CopyableCtx& cls)
+ : Context<Cls, Type>(cls.Env(), cls.Get()) {}
+ };
+
+ // Private copy constructor so that there's no danger of assigning a
+ // temporary LocalRef/GlobalRef to a Ref, and potentially use the Ref
+ // after the source had been freed.
+ Ref(const Ref&) = default;
+
+ protected:
+ static JNIEnv* FindEnv() {
+ return Cls::callingThread == CallingThread::GECKO ? GetGeckoThreadEnv()
+ : GetEnvForThread();
+ }
+
+ Type mInstance;
+
+ // Protected jobject constructor because outside code should be using
+ // Ref::From. Using Ref::From makes it very easy to see which code is using
+ // raw JNI types for future refactoring.
+ explicit Ref(Type instance) : mInstance(instance) {}
+
+ public:
+ using JNIType = Type;
+
+ class AutoLock {
+ friend class Ref<Cls, Type>;
+
+ JNIEnv* const mEnv;
+ Type mInstance;
+
+ explicit AutoLock(Type aInstance)
+ : mEnv(FindEnv()), mInstance(mEnv->NewLocalRef(aInstance)) {
+ mEnv->MonitorEnter(mInstance);
+ MOZ_CATCH_JNI_EXCEPTION(mEnv);
+ }
+
+ public:
+ AutoLock(AutoLock&& aOther)
+ : mEnv(aOther.mEnv), mInstance(aOther.mInstance) {
+ aOther.mInstance = nullptr;
+ }
+
+ ~AutoLock() { Unlock(); }
+
+ void Unlock() {
+ if (mInstance) {
+ mEnv->MonitorExit(mInstance);
+ mEnv->DeleteLocalRef(mInstance);
+ MOZ_CATCH_JNI_EXCEPTION(mEnv);
+ mInstance = nullptr;
+ }
+ }
+ };
+
+ // Construct a Ref form a raw JNI reference.
+ static Ref<Cls, Type> From(JNIType obj) { return Ref<Cls, Type>(obj); }
+
+ // Construct a Ref form a generic object reference.
+ static Ref<Cls, Type> From(const Ref<Object, jobject>& obj) {
+ return Ref<Cls, Type>(JNIType(obj.Get()));
+ }
+
+ MOZ_IMPLICIT Ref(decltype(nullptr)) : mInstance(nullptr) {}
+
+ // Get the raw JNI reference.
+ JNIType Get() const { return mInstance; }
+
+ template <class T>
+ bool IsInstanceOf() const {
+ return FindEnv()->IsInstanceOf(mInstance, typename T::Context().ClassRef());
+ }
+
+ template <class T>
+ typename T::Ref Cast() const {
+#ifdef MOZ_CHECK_JNI
+ MOZ_RELEASE_ASSERT(FindEnv()->IsAssignableFrom(
+ Context<Cls, Type>().ClassRef(), typename T::Context().ClassRef()));
+#endif
+ return T::Ref::From(*this);
+ }
+
+ AutoLock Lock() const { return AutoLock(mInstance); }
+
+ bool operator==(const Ref& other) const {
+ // Treat two references of the same object as being the same.
+ return mInstance == other.mInstance ||
+ JNI_FALSE != FindEnv()->IsSameObject(mInstance, other.mInstance);
+ }
+
+ bool operator!=(const Ref& other) const { return !operator==(other); }
+
+ bool operator==(decltype(nullptr)) const { return !mInstance; }
+
+ bool operator!=(decltype(nullptr)) const { return !!mInstance; }
+
+ CopyableCtx operator->() const { return CopyableCtx(FindEnv(), mInstance); }
+
+ CopyableCtx operator*() const { return operator->(); }
+
+ // Any ref can be cast to an object ref.
+ operator Ref<Object, jobject>() const {
+ return Ref<Object, jobject>(mInstance);
+ }
+
+ // Null checking (e.g. !!ref) using the safe-bool idiom.
+ operator bool_type() const {
+ return mInstance ? &Self::non_null_reference : nullptr;
+ }
+
+ // We don't allow implicit conversion to jobject because that can lead
+ // to easy mistakes such as assigning a temporary LocalRef to a jobject,
+ // and using the jobject after the LocalRef has been freed.
+
+ // We don't allow explicit conversion, to make outside code use Ref::Get.
+ // Using Ref::Get makes it very easy to see which code is using raw JNI
+ // types to make future refactoring easier.
+
+ // operator JNIType() const = delete;
+};
+
+// Represents a calling context for JNI methods.
+template <class Cls, typename Type>
+class Context : public Ref<Cls, Type> {
+ using Ref = jni::Ref<Cls, Type>;
+
+ static jclass sClassRef; // global reference
+
+ protected:
+ JNIEnv* const mEnv;
+
+ public:
+ Context() : Ref(nullptr), mEnv(Ref::FindEnv()) {}
+
+ Context(JNIEnv* env, Type instance) : Ref(instance), mEnv(env) {}
+
+ jclass ClassRef() const {
+ if (!sClassRef) {
+ const jclass cls = GetClassRef(mEnv, Cls::name);
+ sClassRef = jclass(mEnv->NewGlobalRef(cls));
+ mEnv->DeleteLocalRef(cls);
+ }
+ return sClassRef;
+ }
+
+ JNIEnv* Env() const { return mEnv; }
+
+ template <class T>
+ bool IsInstanceOf() const {
+ return mEnv->IsInstanceOf(Ref::mInstance,
+ typename T::Context(mEnv, nullptr).ClassRef());
+ }
+
+ bool operator==(const Ref& other) const {
+ // Treat two references of the same object as being the same.
+ return Ref::mInstance == other.Get() ||
+ JNI_FALSE != mEnv->IsSameObject(Ref::mInstance, other.Get());
+ }
+
+ bool operator!=(const Ref& other) const { return !operator==(other); }
+
+ bool operator==(decltype(nullptr)) const { return !Ref::mInstance; }
+
+ bool operator!=(decltype(nullptr)) const { return !!Ref::mInstance; }
+
+ Cls operator->() const {
+ MOZ_ASSERT(Ref::mInstance, "Null jobject");
+ return Cls(*this);
+ }
+
+ const Context<Cls, Type>& operator*() const { return *this; }
+};
+
+template <class C, typename T>
+jclass Context<C, T>::sClassRef;
+
+template <class Cls, typename Type = jobject>
+class ObjectBase {
+ protected:
+ const jni::Context<Cls, Type>& mCtx;
+
+ jclass ClassRef() const { return mCtx.ClassRef(); }
+ JNIEnv* Env() const { return mCtx.Env(); }
+ Type Instance() const { return mCtx.Get(); }
+
+ public:
+ using Ref = jni::Ref<Cls, Type>;
+ using Context = jni::Context<Cls, Type>;
+ using LocalRef = jni::LocalRef<Cls>;
+ using GlobalRef = jni::GlobalRef<Cls>;
+ using WeakRef = jni::WeakRef<Cls>;
+ using Param = const Ref&;
+
+ static const CallingThread callingThread = CallingThread::ANY;
+ static const char name[];
+
+ explicit ObjectBase(const Context& ctx) : mCtx(ctx) {}
+
+ Cls* operator->() { return static_cast<Cls*>(this); }
+};
+
+// Binding for a plain jobject.
+class Object : public ObjectBase<Object, jobject> {
+ public:
+ explicit Object(const Context& ctx) : ObjectBase<Object, jobject>(ctx) {}
+};
+
+// Binding for a built-in object reference other than jobject.
+template <typename T>
+class TypedObject : public ObjectBase<TypedObject<T>, T> {
+ public:
+ explicit TypedObject(const Context<TypedObject<T>, T>& ctx)
+ : ObjectBase<TypedObject<T>, T>(ctx) {}
+};
+
+// Binding for a boxed primitive object.
+template <typename T>
+class BoxedObject : public ObjectBase<BoxedObject<T>, jobject> {
+ public:
+ explicit BoxedObject(const Context<BoxedObject<T>, jobject>& ctx)
+ : ObjectBase<BoxedObject<T>, jobject>(ctx) {}
+};
+
+template <>
+const char ObjectBase<Object, jobject>::name[];
+template <>
+const char ObjectBase<TypedObject<jstring>, jstring>::name[];
+template <>
+const char ObjectBase<TypedObject<jclass>, jclass>::name[];
+template <>
+const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[];
+template <>
+const char ObjectBase<BoxedObject<jboolean>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jbyte>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jchar>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jshort>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jint>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jlong>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jfloat>, jobject>::name[];
+template <>
+const char ObjectBase<BoxedObject<jdouble>, jobject>::name[];
+template <>
+const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jintArray>, jintArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[];
+template <>
+const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[];
+
+// Define bindings for built-in types.
+using String = TypedObject<jstring>;
+using Class = TypedObject<jclass>;
+using Throwable = TypedObject<jthrowable>;
+
+using Boolean = BoxedObject<jboolean>;
+using Byte = BoxedObject<jbyte>;
+using Character = BoxedObject<jchar>;
+using Short = BoxedObject<jshort>;
+using Integer = BoxedObject<jint>;
+using Long = BoxedObject<jlong>;
+using Float = BoxedObject<jfloat>;
+using Double = BoxedObject<jdouble>;
+
+using BooleanArray = TypedObject<jbooleanArray>;
+using ByteArray = TypedObject<jbyteArray>;
+using CharArray = TypedObject<jcharArray>;
+using ShortArray = TypedObject<jshortArray>;
+using IntArray = TypedObject<jintArray>;
+using LongArray = TypedObject<jlongArray>;
+using FloatArray = TypedObject<jfloatArray>;
+using DoubleArray = TypedObject<jdoubleArray>;
+using ObjectArray = TypedObject<jobjectArray>;
+
+namespace detail {
+
+// See explanation in LocalRef.
+template <class Cls>
+struct GenericObject {
+ using Type = Object;
+};
+template <>
+struct GenericObject<Object> {
+ struct Type {
+ using Ref = jni::Ref<Type, jobject>;
+ using Context = jni::Context<Type, jobject>;
+ };
+};
+template <class Cls>
+struct GenericLocalRef {
+ template <class C>
+ struct Type : jni::Object {};
+};
+template <>
+struct GenericLocalRef<Object> {
+ template <class C>
+ using Type = jni::LocalRef<C>;
+};
+
+} // namespace detail
+
+template <class Cls>
+class LocalRef : public Cls::Context {
+ template <class C>
+ friend class LocalRef;
+
+ using Ctx = typename Cls::Context;
+ using Ref = typename Cls::Ref;
+ using JNIType = typename Ref::JNIType;
+
+ // In order to be able to convert LocalRef<Object> to LocalRef<Cls>, we
+ // need constructors and copy assignment operators that take in a
+ // LocalRef<Object> argument. However, if Cls *is* Object, we would have
+ // duplicated constructors and operators with LocalRef<Object> arguments. To
+ // avoid this conflict, we use GenericObject, which is defined as Object for
+ // LocalRef<non-Object> and defined as a dummy class for LocalRef<Object>.
+ using GenericObject = typename detail::GenericObject<Cls>::Type;
+
+ // Similarly, GenericLocalRef is useed to convert LocalRef<Cls> to,
+ // LocalRef<Object>. It's defined as LocalRef<C> for Cls == Object,
+ // and defined as a dummy template class for Cls != Object.
+ template <class C>
+ using GenericLocalRef =
+ typename detail::GenericLocalRef<Cls>::template Type<C>;
+
+ static JNIType NewLocalRef(JNIEnv* env, JNIType obj) {
+ return JNIType(obj ? env->NewLocalRef(obj) : nullptr);
+ }
+
+ LocalRef(JNIEnv* env, JNIType instance) : Ctx(env, instance) {}
+
+ LocalRef& swap(LocalRef& other) {
+ auto instance = other.mInstance;
+ other.mInstance = Ctx::mInstance;
+ Ctx::mInstance = instance;
+ return *this;
+ }
+
+ public:
+ // Construct a LocalRef from a raw JNI local reference. Unlike Ref::From,
+ // LocalRef::Adopt returns a LocalRef that will delete the local reference
+ // when going out of scope.
+ static LocalRef Adopt(JNIType instance) {
+ return LocalRef(Ref::FindEnv(), instance);
+ }
+
+ static LocalRef Adopt(JNIEnv* env, JNIType instance) {
+ return LocalRef(env, instance);
+ }
+
+ // Copy constructor.
+ LocalRef(const LocalRef<Cls>& ref)
+ : Ctx(ref.mEnv, NewLocalRef(ref.mEnv, ref.mInstance)) {}
+
+ // Move constructor.
+ LocalRef(LocalRef<Cls>&& ref) : Ctx(ref.mEnv, ref.mInstance) {
+ ref.mInstance = nullptr;
+ }
+
+ explicit LocalRef(JNIEnv* env = Ref::FindEnv()) : Ctx(env, nullptr) {}
+
+ // Construct a LocalRef from any Ref,
+ // which means creating a new local reference.
+ MOZ_IMPLICIT LocalRef(const Ref& ref) : Ctx(Ref::FindEnv(), nullptr) {
+ Ctx::mInstance = NewLocalRef(Ctx::mEnv, ref.Get());
+ }
+
+ LocalRef(JNIEnv* env, const Ref& ref)
+ : Ctx(env, NewLocalRef(env, ref.Get())) {}
+
+ // Move a LocalRef<Object> into a LocalRef<Cls> without
+ // creating/deleting local references.
+ MOZ_IMPLICIT LocalRef(LocalRef<GenericObject>&& ref)
+ : Ctx(ref.mEnv, JNIType(ref.mInstance)) {
+ ref.mInstance = nullptr;
+ }
+
+ template <class C>
+ MOZ_IMPLICIT LocalRef(GenericLocalRef<C>&& ref)
+ : Ctx(ref.mEnv, ref.mInstance) {
+ ref.mInstance = nullptr;
+ }
+
+ // Implicitly converts nullptr to LocalRef.
+ MOZ_IMPLICIT LocalRef(decltype(nullptr)) : Ctx(Ref::FindEnv(), nullptr) {}
+
+ ~LocalRef() {
+ if (Ctx::mInstance) {
+ Ctx::mEnv->DeleteLocalRef(Ctx::mInstance);
+ Ctx::mInstance = nullptr;
+ }
+ }
+
+ // Get the raw JNI reference that can be used as a return value.
+ // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
+ typename Ref::JNIType Forget() {
+ const auto obj = Ctx::Get();
+ Ctx::mInstance = nullptr;
+ return obj;
+ }
+
+ LocalRef<Cls>& operator=(LocalRef<Cls> ref) & { return swap(ref); }
+
+ LocalRef<Cls>& operator=(const Ref& ref) & {
+ LocalRef<Cls> newRef(Ctx::mEnv, ref);
+ return swap(newRef);
+ }
+
+ LocalRef<Cls>& operator=(LocalRef<GenericObject>&& ref) & {
+ LocalRef<Cls> newRef(std::move(ref));
+ return swap(newRef);
+ }
+
+ template <class C>
+ LocalRef<Cls>& operator=(GenericLocalRef<C>&& ref) & {
+ LocalRef<Cls> newRef(std::move(ref));
+ return swap(newRef);
+ }
+
+ LocalRef<Cls>& operator=(decltype(nullptr)) & {
+ LocalRef<Cls> newRef(Ctx::mEnv, nullptr);
+ return swap(newRef);
+ }
+};
+
+template <class Cls>
+class GlobalRef : public Cls::Ref {
+ using Ref = typename Cls::Ref;
+ using JNIType = typename Ref::JNIType;
+
+ static JNIType NewGlobalRef(JNIEnv* env, JNIType instance) {
+ return JNIType(instance ? env->NewGlobalRef(instance) : nullptr);
+ }
+
+ GlobalRef& swap(GlobalRef& other) {
+ auto instance = other.mInstance;
+ other.mInstance = Ref::mInstance;
+ Ref::mInstance = instance;
+ return *this;
+ }
+
+ public:
+ GlobalRef() : Ref(nullptr) {}
+
+ // Copy constructor
+ GlobalRef(const GlobalRef& ref)
+ : Ref(NewGlobalRef(GetEnvForThread(), ref.mInstance)) {}
+
+ // Move constructor
+ GlobalRef(GlobalRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; }
+
+ MOZ_IMPLICIT GlobalRef(const Ref& ref)
+ : Ref(NewGlobalRef(GetEnvForThread(), ref.Get())) {}
+
+ GlobalRef(JNIEnv* env, const Ref& ref) : Ref(NewGlobalRef(env, ref.Get())) {}
+
+ MOZ_IMPLICIT GlobalRef(const LocalRef<Cls>& ref)
+ : Ref(NewGlobalRef(ref.Env(), ref.Get())) {}
+
+ // Implicitly converts nullptr to GlobalRef.
+ MOZ_IMPLICIT GlobalRef(decltype(nullptr)) : Ref(nullptr) {}
+
+ ~GlobalRef() {
+ if (Ref::mInstance) {
+ Clear(GetEnvForThread());
+ }
+ }
+
+ // Get the raw JNI reference that can be used as a return value.
+ // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
+ typename Ref::JNIType Forget() {
+ const auto obj = Ref::Get();
+ Ref::mInstance = nullptr;
+ return obj;
+ }
+
+ void Clear(JNIEnv* env) {
+ if (Ref::mInstance) {
+ env->DeleteGlobalRef(Ref::mInstance);
+ Ref::mInstance = nullptr;
+ }
+ }
+
+ GlobalRef<Cls>& operator=(GlobalRef<Cls> ref) & { return swap(ref); }
+
+ GlobalRef<Cls>& operator=(const Ref& ref) & {
+ GlobalRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ GlobalRef<Cls>& operator=(const LocalRef<Cls>& ref) & {
+ GlobalRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ GlobalRef<Cls>& operator=(decltype(nullptr)) & {
+ GlobalRef<Cls> newRef(nullptr);
+ return swap(newRef);
+ }
+};
+
+template <class Cls>
+class WeakRef : public Ref<Cls, jweak> {
+ using Ref = Ref<Cls, jweak>;
+ using JNIType = typename Ref::JNIType;
+
+ static JNIType NewWeakRef(JNIEnv* env, JNIType instance) {
+ return JNIType(instance ? env->NewWeakGlobalRef(instance) : nullptr);
+ }
+
+ WeakRef& swap(WeakRef& other) {
+ auto instance = other.mInstance;
+ other.mInstance = Ref::mInstance;
+ Ref::mInstance = instance;
+ return *this;
+ }
+
+ public:
+ WeakRef() : Ref(nullptr) {}
+
+ // Copy constructor
+ WeakRef(const WeakRef& ref)
+ : Ref(NewWeakRef(GetEnvForThread(), ref.mInstance)) {}
+
+ // Move constructor
+ WeakRef(WeakRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; }
+
+ MOZ_IMPLICIT WeakRef(const Ref& ref)
+ : Ref(NewWeakRef(GetEnvForThread(), ref.Get())) {}
+
+ WeakRef(JNIEnv* env, const Ref& ref) : Ref(NewWeakRef(env, ref.Get())) {}
+
+ MOZ_IMPLICIT WeakRef(const LocalRef<Cls>& ref)
+ : Ref(NewWeakRef(ref.Env(), ref.Get())) {}
+
+ // Implicitly converts nullptr to WeakRef.
+ MOZ_IMPLICIT WeakRef(decltype(nullptr)) : Ref(nullptr) {}
+
+ ~WeakRef() {
+ if (Ref::mInstance) {
+ Clear(GetEnvForThread());
+ }
+ }
+
+ // Get the raw JNI reference that can be used as a return value.
+ // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
+ typename Ref::JNIType Forget() {
+ const auto obj = Ref::Get();
+ Ref::mInstance = nullptr;
+ return obj;
+ }
+
+ void Clear(JNIEnv* env) {
+ if (Ref::mInstance) {
+ env->DeleteWeakGlobalRef(Ref::mInstance);
+ Ref::mInstance = nullptr;
+ }
+ }
+
+ WeakRef<Cls>& operator=(WeakRef<Cls> ref) & { return swap(ref); }
+
+ WeakRef<Cls>& operator=(const Ref& ref) & {
+ WeakRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ WeakRef<Cls>& operator=(const LocalRef<Cls>& ref) & {
+ WeakRef<Cls> newRef(ref);
+ return swap(newRef);
+ }
+
+ WeakRef<Cls>& operator=(decltype(nullptr)) & {
+ WeakRef<Cls> newRef(nullptr);
+ return swap(newRef);
+ }
+
+ void operator->() const = delete;
+ void operator*() const = delete;
+};
+
+template <class Cls>
+class DependentRef : public Cls::Ref {
+ using Ref = typename Cls::Ref;
+
+ public:
+ explicit DependentRef(typename Ref::JNIType instance) : Ref(instance) {}
+
+ DependentRef(const DependentRef& ref) : Ref(ref.Get()) {}
+};
+
+class StringParam;
+
+template <>
+class TypedObject<jstring> : public ObjectBase<TypedObject<jstring>, jstring> {
+ using Base = ObjectBase<TypedObject<jstring>, jstring>;
+
+ public:
+ using Param = const StringParam&;
+
+ explicit TypedObject(const Context& ctx) : Base(ctx) {}
+
+ size_t Length() const {
+ const size_t ret = Base::Env()->GetStringLength(Base::Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ nsString ToString() const {
+ const jchar* const str =
+ Base::Env()->GetStringChars(Base::Instance(), nullptr);
+ const jsize len = Base::Env()->GetStringLength(Base::Instance());
+
+ nsString result(reinterpret_cast<const char16_t*>(str), len);
+ Base::Env()->ReleaseStringChars(Base::Instance(), str);
+ return result;
+ }
+
+ nsCString ToCString() const { return NS_ConvertUTF16toUTF8(ToString()); }
+
+ // Convert jstring to a nsString.
+ operator nsString() const { return ToString(); }
+
+ // Convert jstring to a nsCString.
+ operator nsCString() const { return ToCString(); }
+};
+
+// Define a custom parameter type for String,
+// which accepts both String::Ref and nsAString/nsACString
+class StringParam : public String::Ref {
+ using Ref = String::Ref;
+
+ private:
+ // Not null if we should delete ref on destruction.
+ JNIEnv* const mEnv;
+
+ static jstring GetString(JNIEnv* env, const nsAString& str) {
+ const jstring result = env->NewString(
+ reinterpret_cast<const jchar*>(str.BeginReading()), str.Length());
+ if (!result) {
+ NS_ABORT_OOM(str.Length() * sizeof(char16_t));
+ }
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return result;
+ }
+
+ static jstring GetString(JNIEnv* env, const nsAString& str,
+ const fallible_t&) {
+ const jstring result = env->NewString(
+ reinterpret_cast<const jchar*>(str.BeginReading()), str.Length());
+ if (env->ExceptionCheck()) {
+#ifdef MOZ_CHECK_JNI
+ env->ExceptionDescribe();
+#endif
+ env->ExceptionClear();
+ }
+ return result;
+ }
+
+ static jstring GetString(JNIEnv* env, const nsACString& str,
+ const fallible_t& aFallible) {
+ nsAutoString utf16;
+ if (!CopyUTF8toUTF16(str, utf16, aFallible)) {
+ return nullptr;
+ }
+ return GetString(env, utf16, aFallible);
+ }
+
+ public:
+ MOZ_IMPLICIT StringParam(decltype(nullptr)) : Ref(nullptr), mEnv(nullptr) {}
+
+ MOZ_IMPLICIT StringParam(const Ref& ref) : Ref(ref.Get()), mEnv(nullptr) {}
+
+ MOZ_IMPLICIT StringParam(const nsAString& str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, str)), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsAString& str, JNIEnv* env,
+ const fallible_t& aFallible)
+ : Ref(GetString(env, str, aFallible)), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsLiteralString& str,
+ JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, str)), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const char16_t* str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, nsDependentString(str))), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsACString& str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsACString& str, JNIEnv* env,
+ const fallible_t& aFallible)
+ : Ref(GetString(env, str, aFallible)), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const nsLiteralCString& str,
+ JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}
+
+ MOZ_IMPLICIT StringParam(const char* str, JNIEnv* env = Ref::FindEnv())
+ : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}
+
+ StringParam(StringParam&& other) : Ref(other.Get()), mEnv(other.mEnv) {
+ other.mInstance = nullptr;
+ }
+
+ ~StringParam() {
+ if (mEnv && Get()) {
+ mEnv->DeleteLocalRef(Get());
+ }
+ }
+
+ operator String::LocalRef() const {
+ // We can't return our existing ref because the returned
+ // LocalRef could be freed first, so we need a new local ref.
+ return String::LocalRef(mEnv ? mEnv : Ref::FindEnv(), *this);
+ }
+};
+
+namespace detail {
+template <typename T>
+struct TypeAdapter;
+}
+
+// Ref specialization for arrays.
+template <typename JNIType, class ElementType>
+class ArrayRefBase : public ObjectBase<TypedObject<JNIType>, JNIType> {
+ protected:
+ using Base = ObjectBase<TypedObject<JNIType>, JNIType>;
+
+ public:
+ explicit ArrayRefBase(const Context<TypedObject<JNIType>, JNIType>& ctx)
+ : Base(ctx) {}
+
+ static typename Base::LocalRef New(const ElementType* data, size_t length) {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+ JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
+ auto result = (jenv->*detail::TypeAdapter<ElementType>::NewArray)(length);
+ MOZ_CATCH_JNI_EXCEPTION(jenv);
+ (jenv->*detail::TypeAdapter<ElementType>::SetArray)(
+ result, jsize(0), length, reinterpret_cast<const JNIElemType*>(data));
+ MOZ_CATCH_JNI_EXCEPTION(jenv);
+ return Base::LocalRef::Adopt(jenv, result);
+ }
+
+ static typename Base::LocalRef New(const ElementType* data, size_t length,
+ const fallible_t&) {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+ JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
+ auto result = (jenv->*detail::TypeAdapter<ElementType>::NewArray)(length);
+ if (jenv->ExceptionCheck()) {
+ if (!IsOOMException(jenv)) {
+ // This exception isn't excepted due not to OOM. This is unrecoverable
+ // error.
+ MOZ_CATCH_JNI_EXCEPTION(jenv);
+ }
+#ifdef MOZ_CHECK_JNI
+ jenv->ExceptionDescribe();
+#endif
+ jenv->ExceptionClear();
+ return Base::LocalRef::Adopt(jenv, nullptr);
+ }
+ (jenv->*detail::TypeAdapter<ElementType>::SetArray)(
+ result, jsize(0), length, reinterpret_cast<const JNIElemType*>(data));
+ if (jenv->ExceptionCheck()) {
+ if (!IsOOMException(jenv)) {
+ // This exception isn't excepted due not to OOM. This is unrecoverable
+ // error.
+ MOZ_CATCH_JNI_EXCEPTION(jenv);
+ }
+#ifdef MOZ_CHECK_JNI
+ jenv->ExceptionDescribe();
+#endif
+ jenv->ExceptionClear();
+ jenv->DeleteLocalRef(result);
+ return Base::LocalRef::Adopt(jenv, nullptr);
+ }
+ return Base::LocalRef::Adopt(jenv, result);
+ }
+
+ size_t Length() const {
+ const size_t ret = Base::Env()->GetArrayLength(Base::Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ ElementType GetElement(size_t index) const {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+
+ ElementType ret;
+ (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)(
+ Base::Instance(), jsize(index), 1,
+ reinterpret_cast<JNIElemType*>(&ret));
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ nsTArray<ElementType> GetElements() const {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+
+ const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+
+ nsTArray<ElementType> array(len);
+ array.SetLength(len);
+ CopyTo(array.Elements(), len);
+ return array;
+ }
+
+ // returns number of elements copied
+ size_t CopyTo(ElementType* buffer, size_t size) const {
+ using JNIElemType = typename detail::TypeAdapter<ElementType>::JNIType;
+ static_assert(sizeof(ElementType) == sizeof(JNIElemType),
+ "Size of native type must match size of JNI type");
+
+ const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+ const size_t amountToCopy = (len > size ? size : len);
+ (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)(
+ Base::Instance(), 0, jsize(amountToCopy),
+ reinterpret_cast<JNIElemType*>(buffer));
+ return amountToCopy;
+ }
+
+ ElementType operator[](size_t index) const { return GetElement(index); }
+
+ operator nsTArray<ElementType>() const { return GetElements(); }
+};
+
+#define DEFINE_PRIMITIVE_ARRAY_REF_HEADER(JNIType, ElementType) \
+ template <> \
+ class TypedObject<JNIType> : public ArrayRefBase<JNIType, ElementType> { \
+ public: \
+ explicit TypedObject(const Context& ctx) \
+ : ArrayRefBase<JNIType, ElementType>(ctx) {} \
+ static typename Base::LocalRef From(const nsTArray<ElementType>& aArray) { \
+ return New(aArray.Elements(), aArray.Length()); \
+ }
+
+#define DEFINE_PRIMITIVE_ARRAY_REF_FOOTER }
+
+#define DEFINE_PRIMITIVE_ARRAY_REF_FROM_IMPLICIT_CONVERSION(ElementType, \
+ ConvertFromType) \
+ static typename Base::LocalRef From( \
+ const nsTArray<ConvertFromType>& aArray) { \
+ return New(reinterpret_cast<const ElementType*>(aArray.Elements()), \
+ aArray.Length()); \
+ }
+
+#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType) \
+ DEFINE_PRIMITIVE_ARRAY_REF_HEADER(JNIType, ElementType) \
+ DEFINE_PRIMITIVE_ARRAY_REF_FOOTER
+
+#define DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION( \
+ JNIType, ElementType, ConvertFromType) \
+ DEFINE_PRIMITIVE_ARRAY_REF_HEADER(JNIType, ElementType) \
+ DEFINE_PRIMITIVE_ARRAY_REF_FROM_IMPLICIT_CONVERSION(ElementType, \
+ ConvertFromType) \
+ DEFINE_PRIMITIVE_ARRAY_REF_FOOTER
+
+DEFINE_PRIMITIVE_ARRAY_REF(jbooleanArray, bool);
+DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jbyteArray, int8_t,
+ uint8_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jcharArray, char16_t);
+DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jshortArray, int16_t,
+ uint16_t);
+DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jintArray, int32_t,
+ uint32_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jfloatArray, float);
+DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jlongArray, int64_t,
+ uint64_t);
+DEFINE_PRIMITIVE_ARRAY_REF(jdoubleArray, double);
+
+#undef DEFINE_PRIMITIVE_ARRAY_REF
+#undef DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION
+#undef DEFINE_PRIMITIVE_ARRAY_HEADER
+#undef DEFINE_PRIMITIVE_ARRAY_FROM_IMPLICIT_CONVERSION
+#undef DEFINE_PRIMITIVE_ARRAY_FOOTER
+
+class ByteBuffer : public ObjectBase<ByteBuffer, jobject> {
+ public:
+ explicit ByteBuffer(const Context& ctx)
+ : ObjectBase<ByteBuffer, jobject>(ctx) {}
+
+ static LocalRef New(void* data, size_t capacity) {
+ JNIEnv* const env = GetEnvForThread();
+ const auto ret =
+ LocalRef::Adopt(env, env->NewDirectByteBuffer(data, jlong(capacity)));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return ret;
+ }
+
+ static LocalRef New(void* data, size_t capacity, const fallible_t&) {
+ JNIEnv* const env = GetEnvForThread();
+ const jobject result = env->NewDirectByteBuffer(data, jlong(capacity));
+ if (env->ExceptionCheck()) {
+ if (!IsOOMException(env)) {
+ // This exception isn't excepted due not to OOM. This is unrecoverable
+ // error.
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ }
+#ifdef MOZ_CHECK_JNI
+ env->ExceptionDescribe();
+#endif
+ env->ExceptionClear();
+ return LocalRef::Adopt(env, nullptr);
+ }
+ return LocalRef::Adopt(env, result);
+ }
+
+ void* Address() {
+ void* const ret = Env()->GetDirectBufferAddress(Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Env());
+ return ret;
+ }
+
+ size_t Capacity() {
+ const size_t ret = size_t(Env()->GetDirectBufferCapacity(Instance()));
+ MOZ_CATCH_JNI_EXCEPTION(Env());
+ return ret;
+ }
+};
+
+template <>
+const char ObjectBase<ByteBuffer, jobject>::name[];
+
+template <>
+class TypedObject<jobjectArray>
+ : public ObjectBase<TypedObject<jobjectArray>, jobjectArray> {
+ using Base = ObjectBase<TypedObject<jobjectArray>, jobjectArray>;
+
+ public:
+ template <class Cls = Object>
+ static Base::LocalRef New(size_t length,
+ typename Cls::Param initialElement = nullptr) {
+ JNIEnv* const env = GetEnvForThread();
+ jobjectArray array = env->NewObjectArray(
+ jsize(length), typename Cls::Context(env, nullptr).ClassRef(),
+ initialElement.Get());
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return Base::LocalRef::Adopt(env, array);
+ }
+
+ explicit TypedObject(const Context& ctx) : Base(ctx) {}
+
+ size_t Length() const {
+ const size_t ret = Base::Env()->GetArrayLength(Base::Instance());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ Object::LocalRef GetElement(size_t index) const {
+ auto ret = Object::LocalRef::Adopt(
+ Base::Env(),
+ Base::Env()->GetObjectArrayElement(Base::Instance(), jsize(index)));
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ return ret;
+ }
+
+ nsTArray<Object::LocalRef> GetElements() const {
+ const jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+
+ nsTArray<Object::LocalRef> array((size_t(len)));
+ for (jsize i = 0; i < len; i++) {
+ array.AppendElement(Object::LocalRef::Adopt(
+ Base::Env(),
+ Base::Env()->GetObjectArrayElement(Base::Instance(), i)));
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ }
+ return array;
+ }
+
+ Object::LocalRef operator[](size_t index) const { return GetElement(index); }
+
+ operator nsTArray<Object::LocalRef>() const { return GetElements(); }
+
+ void SetElement(size_t index, Object::Param element) const {
+ Base::Env()->SetObjectArrayElement(Base::Instance(), jsize(index),
+ element.Get());
+ MOZ_CATCH_JNI_EXCEPTION(Base::Env());
+ }
+};
+
+// Support conversion from LocalRef<T>* to LocalRef<Object>*:
+// LocalRef<Foo> foo;
+// Foo::GetFoo(&foo); // error because parameter type is LocalRef<Object>*.
+// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
+template <class Cls>
+class ReturnToLocal {
+ private:
+ LocalRef<Cls>* const localRef;
+ LocalRef<Object> objRef;
+
+ public:
+ explicit ReturnToLocal(LocalRef<Cls>* ref) : localRef(ref) {}
+ operator LocalRef<Object>*() { return &objRef; }
+
+ ~ReturnToLocal() {
+ if (objRef) {
+ *localRef = std::move(objRef);
+ }
+ }
+};
+
+template <class Cls>
+ReturnToLocal<Cls> ReturnTo(LocalRef<Cls>* ref) {
+ return ReturnToLocal<Cls>(ref);
+}
+
+// Support conversion from GlobalRef<T>* to LocalRef<Object/T>*:
+// GlobalRef<Foo> foo;
+// Foo::GetFoo(&foo); // error because parameter type is LocalRef<Foo>*.
+// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
+template <class Cls>
+class ReturnToGlobal {
+ private:
+ GlobalRef<Cls>* const globalRef;
+ LocalRef<Object> objRef;
+ LocalRef<Cls> clsRef;
+
+ public:
+ explicit ReturnToGlobal(GlobalRef<Cls>* ref) : globalRef(ref) {}
+ operator LocalRef<Object>*() { return &objRef; }
+ operator LocalRef<Cls>*() { return &clsRef; }
+
+ ~ReturnToGlobal() {
+ if (objRef) {
+ *globalRef = (clsRef = std::move(objRef));
+ } else if (clsRef) {
+ *globalRef = clsRef;
+ }
+ }
+};
+
+template <class Cls>
+ReturnToGlobal<Cls> ReturnTo(GlobalRef<Cls>* ref) {
+ return ReturnToGlobal<Cls>(ref);
+}
+
+// Make a LocalRef<T> from any other Ref<T>
+template <typename Cls, typename JNIType>
+LocalRef<Cls> ToLocalRef(const Ref<Cls, JNIType>& aRef) {
+ return LocalRef<Cls>(aRef);
+}
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Refs_h__
diff --git a/widget/android/jni/TypeAdapter.h b/widget/android/jni/TypeAdapter.h
new file mode 100644
index 0000000000..5ab1e14bf5
--- /dev/null
+++ b/widget/android/jni/TypeAdapter.h
@@ -0,0 +1,71 @@
+/* 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/. */
+
+#ifndef mozilla_jni_TypeAdapter_h__
+#define mozilla_jni_TypeAdapter_h__
+
+#include <jni.h>
+
+#include "mozilla/jni/Refs.h"
+
+namespace mozilla {
+namespace jni {
+namespace detail {
+
+// TypeAdapter specializations are the interfaces between native/C++ types such
+// as int32_t and JNI types such as jint. The template parameter T is the native
+// type, and each TypeAdapter specialization can have the following members:
+//
+// * Call: JNIEnv member pointer for making a method call that returns T.
+// * StaticCall: JNIEnv member pointer for making a static call that returns T.
+// * Get: JNIEnv member pointer for getting a field of type T.
+// * StaticGet: JNIEnv member pointer for getting a static field of type T.
+// * Set: JNIEnv member pointer for setting a field of type T.
+// * StaticGet: JNIEnv member pointer for setting a static field of type T.
+// * ToNative: static function that converts the JNI type to the native type.
+// * FromNative: static function that converts the native type to the JNI type.
+
+template <typename T>
+struct TypeAdapter;
+
+#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName) \
+ \
+ template <> \
+ struct TypeAdapter<NativeType> { \
+ using JNI##Type = JNIType; \
+ \
+ static constexpr auto Call = &JNIEnv::Call##JNIName##MethodA; \
+ static constexpr auto StaticCall = &JNIEnv::CallStatic##JNIName##MethodA; \
+ static constexpr auto Get = &JNIEnv::Get##JNIName##Field; \
+ static constexpr auto StaticGet = &JNIEnv::GetStatic##JNIName##Field; \
+ static constexpr auto Set = &JNIEnv::Set##JNIName##Field; \
+ static constexpr auto StaticSet = &JNIEnv::SetStatic##JNIName##Field; \
+ static constexpr auto GetArray = &JNIEnv::Get##JNIName##ArrayRegion; \
+ static constexpr auto SetArray = &JNIEnv::Set##JNIName##ArrayRegion; \
+ static constexpr auto NewArray = &JNIEnv::New##JNIName##Array; \
+ \
+ static JNIType FromNative(JNIEnv*, NativeType val) { \
+ return static_cast<JNIType>(val); \
+ } \
+ static NativeType ToNative(JNIEnv*, JNIType val) { \
+ return static_cast<NativeType>(val); \
+ } \
+ }
+
+DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double);
+
+#undef DEFINE_PRIMITIVE_TYPE_ADAPTER
+
+} // namespace detail
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Types_h__
diff --git a/widget/android/jni/Types.h b/widget/android/jni/Types.h
new file mode 100644
index 0000000000..1fe5fa4400
--- /dev/null
+++ b/widget/android/jni/Types.h
@@ -0,0 +1,123 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_Types_h__
+#define mozilla_jni_Types_h__
+
+#include <jni.h>
+
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/TypeAdapter.h"
+
+namespace mozilla {
+namespace jni {
+namespace detail {
+
+// TypeAdapter specializations are the interfaces between native/C++ types such
+// as int32_t and JNI types such as jint. The template parameter T is the native
+// type, and each TypeAdapter specialization can have the following members:
+//
+// * Call: JNIEnv member pointer for making a method call that returns T.
+// * StaticCall: JNIEnv member pointer for making a static call that returns T.
+// * Get: JNIEnv member pointer for getting a field of type T.
+// * StaticGet: JNIEnv member pointer for getting a static field of type T.
+// * Set: JNIEnv member pointer for setting a field of type T.
+// * StaticGet: JNIEnv member pointer for setting a static field of type T.
+// * ToNative: static function that converts the JNI type to the native type.
+// * FromNative: static function that converts the native type to the JNI type.
+
+// TypeAdapter<LocalRef<Cls>> applies when jobject is a return value.
+template <class Cls>
+struct TypeAdapter<LocalRef<Cls>> {
+ using JNIType = typename Cls::Ref::JNIType;
+
+ static constexpr auto Call = &JNIEnv::CallObjectMethodA;
+ static constexpr auto StaticCall = &JNIEnv::CallStaticObjectMethodA;
+ static constexpr auto Get = &JNIEnv::GetObjectField;
+ static constexpr auto StaticGet = &JNIEnv::GetStaticObjectField;
+
+ // Declare instance as jobject because JNI methods return
+ // jobject even if the return value is really jstring, etc.
+ static LocalRef<Cls> ToNative(JNIEnv* env, jobject instance) {
+ return LocalRef<Cls>::Adopt(env, JNIType(instance));
+ }
+
+ static JNIType FromNative(JNIEnv*, LocalRef<Cls>&& instance) {
+ return instance.Forget();
+ }
+};
+
+// clang is picky about function types, including attributes that modify the
+// calling convention, lining up. GCC appears to be somewhat less so.
+#ifdef __clang__
+# define MOZ_JNICALL_ABI JNICALL
+#else
+# define MOZ_JNICALL_ABI
+#endif
+
+// NDK r18 made jvalue* method parameters const. We detect the change directly
+// instead of using ndk-version.h in order to remain compatible with r15 for
+// now, which doesn't include those headers.
+class CallArgs {
+ static const jvalue* test(void (JNIEnv::*)(jobject, jmethodID,
+ const jvalue*));
+ static jvalue* test(void (JNIEnv::*)(jobject, jmethodID, jvalue*));
+
+ public:
+ using JValueType = decltype(test(&JNIEnv::CallVoidMethodA));
+};
+
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Call)(
+ jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI;
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticCall)(
+ jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI;
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Get)(jobject, jfieldID);
+template <class Cls>
+constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticGet)(jclass,
+ jfieldID);
+
+// TypeAdapter<Ref<Cls>> applies when jobject is a parameter value.
+template <class Cls, typename T>
+struct TypeAdapter<Ref<Cls, T>> {
+ using JNIType = typename Ref<Cls, T>::JNIType;
+
+ static constexpr auto Set = &JNIEnv::SetObjectField;
+ static constexpr auto StaticSet = &JNIEnv::SetStaticObjectField;
+
+ static DependentRef<Cls> ToNative(JNIEnv* env, JNIType instance) {
+ return DependentRef<Cls>(instance);
+ }
+
+ static JNIType FromNative(JNIEnv*, const Ref<Cls, T>& instance) {
+ return instance.Get();
+ }
+};
+
+template <class Cls, typename T>
+constexpr void (JNIEnv::*TypeAdapter<Ref<Cls, T>>::Set)(jobject, jfieldID,
+ jobject);
+template <class Cls, typename T>
+constexpr void (JNIEnv::*TypeAdapter<Ref<Cls, T>>::StaticSet)(jclass, jfieldID,
+ jobject);
+
+// jstring has its own Param type.
+template <>
+struct TypeAdapter<StringParam> : public TypeAdapter<String::Ref> {};
+
+template <class Cls>
+struct TypeAdapter<const Cls&> : public TypeAdapter<Cls> {};
+
+} // namespace detail
+
+using namespace detail;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Types_h__
diff --git a/widget/android/jni/Utils.cpp b/widget/android/jni/Utils.cpp
new file mode 100644
index 0000000000..08164e4950
--- /dev/null
+++ b/widget/android/jni/Utils.cpp
@@ -0,0 +1,348 @@
+/* -*- 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 "Utils.h"
+#include "Types.h"
+
+#include <android/log.h>
+#include <pthread.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoThreadWrappers.h"
+
+#include "AndroidBuild.h"
+#include "nsAppShell.h"
+#include "nsExceptionHandler.h"
+
+namespace mozilla {
+namespace jni {
+
+namespace detail {
+
+#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName) \
+ \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Call)( \
+ jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall)( \
+ jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Get)(jobject, jfieldID) \
+ ABIName; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticGet)( \
+ jclass, jfieldID) ABIName; \
+ constexpr void (JNIEnv::*TypeAdapter<NativeType>::Set)(jobject, jfieldID, \
+ JNIType) ABIName; \
+ constexpr void (JNIEnv::*TypeAdapter<NativeType>::StaticSet)( \
+ jclass, jfieldID, JNIType) ABIName; \
+ constexpr void (JNIEnv::*TypeAdapter<NativeType>::GetArray)( \
+ JNIType##Array, jsize, jsize, JNIType*)
+
+DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long, /*nothing*/);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float, MOZ_JNICALL_ABI);
+DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double, MOZ_JNICALL_ABI);
+
+#undef DEFINE_PRIMITIVE_TYPE_ADAPTER
+
+} // namespace detail
+
+template <>
+const char ObjectBase<Object, jobject>::name[] = "java/lang/Object";
+template <>
+const char ObjectBase<TypedObject<jstring>, jstring>::name[] =
+ "java/lang/String";
+template <>
+const char ObjectBase<TypedObject<jclass>, jclass>::name[] = "java/lang/Class";
+template <>
+const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[] =
+ "java/lang/Throwable";
+template <>
+const char ObjectBase<BoxedObject<jboolean>, jobject>::name[] =
+ "java/lang/Boolean";
+template <>
+const char ObjectBase<BoxedObject<jbyte>, jobject>::name[] = "java/lang/Byte";
+template <>
+const char ObjectBase<BoxedObject<jchar>, jobject>::name[] =
+ "java/lang/Character";
+template <>
+const char ObjectBase<BoxedObject<jshort>, jobject>::name[] = "java/lang/Short";
+template <>
+const char ObjectBase<BoxedObject<jint>, jobject>::name[] = "java/lang/Integer";
+template <>
+const char ObjectBase<BoxedObject<jlong>, jobject>::name[] = "java/lang/Long";
+template <>
+const char ObjectBase<BoxedObject<jfloat>, jobject>::name[] = "java/lang/Float";
+template <>
+const char ObjectBase<BoxedObject<jdouble>, jobject>::name[] =
+ "java/lang/Double";
+template <>
+const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[] = "[Z";
+template <>
+const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[] = "[B";
+template <>
+const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[] = "[C";
+template <>
+const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[] = "[S";
+template <>
+const char ObjectBase<TypedObject<jintArray>, jintArray>::name[] = "[I";
+template <>
+const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[] = "[J";
+template <>
+const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[] = "[F";
+template <>
+const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[] = "[D";
+template <>
+const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[] =
+ "[Ljava/lang/Object;";
+template <>
+const char ObjectBase<ByteBuffer, jobject>::name[] = "java/nio/ByteBuffer";
+
+JavaVM* sJavaVM;
+JNIEnv* sGeckoThreadEnv;
+
+namespace {
+
+pthread_key_t sThreadEnvKey;
+jclass sOOMErrorClass;
+jobject sClassLoader;
+jmethodID sClassLoaderLoadClass;
+
+void UnregisterThreadEnv(void* env) {
+ if (!env) {
+ // We were never attached.
+ return;
+ }
+ // The thread may have already been detached. In that case, it's still
+ // okay to call DetachCurrentThread(); it'll simply return an error.
+ // However, we must not access | env | because it may be invalid.
+ MOZ_ASSERT(sJavaVM);
+ sJavaVM->DetachCurrentThread();
+}
+
+} // namespace
+
+void SetGeckoThreadEnv(JNIEnv* aEnv) {
+ MOZ_ASSERT(aEnv);
+ MOZ_ASSERT(!sGeckoThreadEnv || sGeckoThreadEnv == aEnv);
+
+ if (!sGeckoThreadEnv &&
+ pthread_key_create(&sThreadEnvKey, UnregisterThreadEnv)) {
+ MOZ_CRASH("Failed to initialize required TLS");
+ }
+
+ sGeckoThreadEnv = aEnv;
+ MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, aEnv));
+
+ MOZ_ALWAYS_TRUE(!aEnv->GetJavaVM(&sJavaVM));
+ MOZ_ASSERT(sJavaVM);
+
+ sOOMErrorClass =
+ Class::GlobalRef(
+ Class::LocalRef::Adopt(aEnv->FindClass("java/lang/OutOfMemoryError")))
+ .Forget();
+ aEnv->ExceptionClear();
+
+ sClassLoader = Object::GlobalRef(java::GeckoThread::ClsLoader()).Forget();
+ sClassLoaderLoadClass = aEnv->GetMethodID(
+ Class::LocalRef::Adopt(aEnv->GetObjectClass(sClassLoader)).Get(),
+ "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+ MOZ_ASSERT(sClassLoader && sClassLoaderLoadClass);
+}
+
+JNIEnv* GetEnvForThread() {
+ MOZ_ASSERT(sGeckoThreadEnv);
+
+ JNIEnv* env = static_cast<JNIEnv*>(pthread_getspecific(sThreadEnvKey));
+ if (env) {
+ return env;
+ }
+
+ // We don't have a saved JNIEnv, so try to get one.
+ // AttachCurrentThread() does the same thing as GetEnv() when a thread is
+ // already attached, so we don't have to call GetEnv() at all.
+ if (!sJavaVM->AttachCurrentThread(&env, nullptr)) {
+ MOZ_ASSERT(env);
+ MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, env));
+ return env;
+ }
+
+ MOZ_CRASH("Failed to get JNIEnv for thread");
+ return nullptr; // unreachable
+}
+
+bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage) {
+ MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+
+ Class::LocalRef cls = Class::LocalRef::Adopt(aEnv->FindClass(aClass));
+ MOZ_ASSERT(cls, "Cannot find exception class");
+
+ return !aEnv->ThrowNew(cls.Get(), aMessage);
+}
+
+bool HandleUncaughtException(JNIEnv* aEnv) {
+ MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+
+ if (!aEnv->ExceptionCheck()) {
+ return false;
+ }
+
+#ifdef MOZ_CHECK_JNI
+ aEnv->ExceptionDescribe();
+#endif
+
+ Throwable::LocalRef e =
+ Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred());
+ MOZ_ASSERT(e);
+ aEnv->ExceptionClear();
+
+ String::LocalRef stack = java::GeckoAppShell::GetExceptionStackTrace(e);
+ if (stack && ReportException(aEnv, e.Get(), stack.Get())) {
+ return true;
+ }
+
+ aEnv->ExceptionClear();
+ java::GeckoAppShell::HandleUncaughtException(e);
+
+ if (NS_WARN_IF(aEnv->ExceptionCheck())) {
+ aEnv->ExceptionDescribe();
+ aEnv->ExceptionClear();
+ }
+
+ return true;
+}
+
+bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack) {
+ bool result = true;
+
+ result &= NS_SUCCEEDED(CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::JavaStackTrace,
+ String::Ref::From(aStack)->ToCString()));
+
+ auto appNotes = java::GeckoAppShell::GetAppNotes();
+ if (NS_WARN_IF(aEnv->ExceptionCheck())) {
+ aEnv->ExceptionDescribe();
+ aEnv->ExceptionClear();
+ } else if (appNotes) {
+ CrashReporter::AppendAppNotesToCrashReport("\n"_ns + appNotes->ToCString());
+ }
+
+ if (sOOMErrorClass && aEnv->IsInstanceOf(aExc, sOOMErrorClass)) {
+ NS_ABORT_OOM(0); // Unknown OOM size
+ }
+ return result;
+}
+
+namespace {
+
+jclass sJNIObjectClass;
+jfieldID sJNIObjectHandleField;
+
+bool EnsureJNIObject(JNIEnv* env, jobject instance) {
+ if (!sJNIObjectClass) {
+ sJNIObjectClass =
+ Class::GlobalRef(Class::LocalRef::Adopt(GetClassRef(
+ env, "org/mozilla/gecko/mozglue/JNIObject")))
+ .Forget();
+
+ sJNIObjectHandleField = env->GetFieldID(sJNIObjectClass, "mHandle", "J");
+ }
+
+ MOZ_ASSERT(env->IsInstanceOf(instance, sJNIObjectClass),
+ "Java class is not derived from JNIObject");
+ return true;
+}
+
+} // namespace
+
+uintptr_t GetNativeHandle(JNIEnv* env, jobject instance) {
+ if (!EnsureJNIObject(env, instance)) {
+ return 0;
+ }
+
+ return static_cast<uintptr_t>(
+ env->GetLongField(instance, sJNIObjectHandleField));
+}
+
+void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle) {
+ if (!EnsureJNIObject(env, instance)) {
+ return;
+ }
+
+ env->SetLongField(instance, sJNIObjectHandleField,
+ static_cast<jlong>(handle));
+}
+
+jclass GetClassRef(JNIEnv* aEnv, const char* aClassName) {
+ // First try the default class loader.
+ auto classRef = Class::LocalRef::Adopt(aEnv, aEnv->FindClass(aClassName));
+
+ if ((!classRef || aEnv->ExceptionCheck()) && sClassLoader) {
+ // If the default class loader failed but we have an app class loader, try
+ // that. Clear the pending exception from failed FindClass call above.
+ aEnv->ExceptionClear();
+ classRef = Class::LocalRef::Adopt(
+ aEnv,
+ jclass(aEnv->CallObjectMethod(sClassLoader, sClassLoaderLoadClass,
+ StringParam(aClassName, aEnv).Get())));
+ }
+
+ if (classRef && !aEnv->ExceptionCheck()) {
+ return classRef.Forget();
+ }
+
+ __android_log_print(
+ ANDROID_LOG_ERROR, "Gecko",
+ ">>> FATAL JNI ERROR! FindClass(\"%s\") failed. "
+ "Does the class require a newer API version? "
+ "Or did ProGuard optimize away something it shouldn't have?",
+ aClassName);
+ aEnv->ExceptionDescribe();
+ MOZ_CRASH("Cannot find JNI class");
+ return nullptr;
+}
+
+void DispatchToGeckoPriorityQueue(already_AddRefed<nsIRunnable> aCall) {
+ class RunnableEvent : public nsAppShell::Event {
+ nsCOMPtr<nsIRunnable> mCall;
+
+ public:
+ explicit RunnableEvent(already_AddRefed<nsIRunnable> aCall)
+ : mCall(aCall) {}
+ void Run() override { NS_ENSURE_SUCCESS_VOID(mCall->Run()); }
+ };
+
+ nsAppShell::PostEvent(MakeUnique<RunnableEvent>(std::move(aCall)));
+}
+
+int GetAPIVersion() {
+ static int32_t apiVersion = 0;
+ if (!apiVersion && IsAvailable()) {
+ apiVersion = java::sdk::Build::VERSION::SDK_INT();
+ }
+ return apiVersion;
+}
+
+pid_t GetUIThreadId() {
+ static pid_t uiThreadId;
+ if (!uiThreadId) {
+ uiThreadId = pid_t(java::GeckoThread::UiThreadId());
+ }
+ return uiThreadId;
+}
+
+bool IsOOMException(JNIEnv* aEnv) {
+ MOZ_ASSERT(aEnv->ExceptionCheck());
+ Throwable::LocalRef e =
+ Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred());
+ return sOOMErrorClass && aEnv->IsInstanceOf(e.Get(), sOOMErrorClass);
+}
+
+} // namespace jni
+} // namespace mozilla
diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h
new file mode 100644
index 0000000000..adcafeb44e
--- /dev/null
+++ b/widget/android/jni/Utils.h
@@ -0,0 +1,150 @@
+/* -*- 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/. */
+
+#ifndef mozilla_jni_Utils_h__
+#define mozilla_jni_Utils_h__
+
+#include <jni.h>
+
+#include "nsIRunnable.h"
+
+#include "mozilla/UniquePtr.h"
+
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+# define MOZ_CHECK_JNI
+#endif
+
+#ifdef MOZ_CHECK_JNI
+# include <unistd.h>
+# include "mozilla/Assertions.h"
+# include "APKOpen.h"
+# include "MainThreadUtils.h"
+#endif
+
+namespace mozilla {
+namespace jni {
+
+// How exception during a JNI call should be treated.
+enum class ExceptionMode {
+ // Abort on unhandled excepion (default).
+ ABORT,
+ // Ignore the exception and return to caller.
+ IGNORE,
+ // Catch any exception and return a nsresult.
+ NSRESULT,
+};
+
+// Thread that a particular JNI call is allowed on.
+enum class CallingThread {
+ // Can be called from any thread (default).
+ ANY,
+ // Can be called from the Gecko thread.
+ GECKO,
+ // Can be called from the Java UI thread.
+ UI,
+};
+
+// If and where a JNI call will be dispatched.
+enum class DispatchTarget {
+ // Call happens synchronously on the calling thread (default).
+ CURRENT,
+ // Call happens synchronously on the calling thread, but the call is
+ // wrapped in a function object and is passed thru UsesNativeCallProxy.
+ // Method must return void.
+ PROXY,
+ // Call is dispatched asynchronously on the Gecko thread to the XPCOM
+ // (nsThread) event queue. Method must return void.
+ GECKO,
+ // Call is dispatched asynchronously on the Gecko thread to the widget
+ // (nsAppShell) event queue. In most cases, events in the widget event
+ // queue (aka native event queue) are favored over events in the XPCOM
+ // event queue. Method must return void.
+ GECKO_PRIORITY,
+};
+
+extern JavaVM* sJavaVM;
+extern JNIEnv* sGeckoThreadEnv;
+
+inline bool IsAvailable() { return !!sGeckoThreadEnv; }
+
+inline JavaVM* GetVM() {
+#ifdef MOZ_CHECK_JNI
+ MOZ_ASSERT(sJavaVM);
+#endif
+ return sJavaVM;
+}
+
+inline JNIEnv* GetGeckoThreadEnv() {
+#ifdef MOZ_CHECK_JNI
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Must be on Gecko thread");
+ MOZ_RELEASE_ASSERT(sGeckoThreadEnv, "Must have a JNIEnv");
+#endif
+ return sGeckoThreadEnv;
+}
+
+void SetGeckoThreadEnv(JNIEnv* aEnv);
+
+JNIEnv* GetEnvForThread();
+
+#ifdef MOZ_CHECK_JNI
+# define MOZ_ASSERT_JNI_THREAD(thread) \
+ do { \
+ if ((thread) == mozilla::jni::CallingThread::GECKO) { \
+ MOZ_RELEASE_ASSERT(::NS_IsMainThread()); \
+ } else if ((thread) == mozilla::jni::CallingThread::UI) { \
+ const bool isOnUiThread = (GetUIThreadId() == ::gettid()); \
+ MOZ_RELEASE_ASSERT(isOnUiThread); \
+ } \
+ } while (0)
+#else
+# define MOZ_ASSERT_JNI_THREAD(thread) \
+ do { \
+ } while (0)
+#endif
+
+bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage);
+
+inline bool ThrowException(JNIEnv* aEnv, const char* aMessage) {
+ return ThrowException(aEnv, "java/lang/Exception", aMessage);
+}
+
+inline bool ThrowException(const char* aClass, const char* aMessage) {
+ return ThrowException(GetEnvForThread(), aClass, aMessage);
+}
+
+inline bool ThrowException(const char* aMessage) {
+ return ThrowException(GetEnvForThread(), aMessage);
+}
+
+bool HandleUncaughtException(JNIEnv* aEnv);
+
+bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack);
+
+#define MOZ_CATCH_JNI_EXCEPTION(env) \
+ do { \
+ if (mozilla::jni::HandleUncaughtException((env))) { \
+ MOZ_CRASH("JNI exception"); \
+ } \
+ } while (0)
+
+uintptr_t GetNativeHandle(JNIEnv* env, jobject instance);
+
+void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle);
+
+jclass GetClassRef(JNIEnv* aEnv, const char* aClassName);
+
+void DispatchToGeckoPriorityQueue(already_AddRefed<nsIRunnable> aCall);
+
+int GetAPIVersion();
+
+pid_t GetUIThreadId();
+
+bool IsOOMException(JNIEnv* aEnv);
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Utils_h__
diff --git a/widget/android/jni/moz.build b/widget/android/jni/moz.build
new file mode 100644
index 0000000000..53bef6663f
--- /dev/null
+++ b/widget/android/jni/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("GeckoView", "General")
+
+EXPORTS.mozilla.jni += [
+ "Accessors.h",
+ "Conversions.h",
+ "GeckoBundleUtils.h",
+ "GeckoResultUtils.h",
+ "Natives.h",
+ "NativesInlines.h",
+ "Refs.h",
+ "TypeAdapter.h",
+ "Types.h",
+ "Utils.h",
+]
+
+UNIFIED_SOURCES += [
+ "Conversions.cpp",
+ "GeckoBundleUtils.cpp",
+ "Utils.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/widget",
+ "/widget/android",
+]