diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /widget/android/jni | |
parent | Initial commit. (diff) | |
download | firefox-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.h | 251 | ||||
-rw-r--r-- | widget/android/jni/Conversions.cpp | 115 | ||||
-rw-r--r-- | widget/android/jni/Conversions.h | 23 | ||||
-rw-r--r-- | widget/android/jni/GeckoBundleUtils.cpp | 309 | ||||
-rw-r--r-- | widget/android/jni/GeckoBundleUtils.h | 46 | ||||
-rw-r--r-- | widget/android/jni/GeckoResultUtils.h | 54 | ||||
-rw-r--r-- | widget/android/jni/Natives.h | 1540 | ||||
-rw-r--r-- | widget/android/jni/NativesInlines.h | 116 | ||||
-rw-r--r-- | widget/android/jni/Refs.h | 1135 | ||||
-rw-r--r-- | widget/android/jni/TypeAdapter.h | 71 | ||||
-rw-r--r-- | widget/android/jni/Types.h | 123 | ||||
-rw-r--r-- | widget/android/jni/Utils.cpp | 348 | ||||
-rw-r--r-- | widget/android/jni/Utils.h | 150 | ||||
-rw-r--r-- | widget/android/jni/moz.build | 36 |
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", +] |