diff options
Diffstat (limited to 'widget/android/jni')
-rw-r--r-- | widget/android/jni/Accessors.h | 274 | ||||
-rw-r--r-- | widget/android/jni/Natives.h | 707 | ||||
-rw-r--r-- | widget/android/jni/Refs.h | 953 | ||||
-rw-r--r-- | widget/android/jni/Types.h | 140 | ||||
-rw-r--r-- | widget/android/jni/Utils.cpp | 301 | ||||
-rw-r--r-- | widget/android/jni/Utils.h | 147 | ||||
-rw-r--r-- | widget/android/jni/moz.build | 24 |
7 files changed, 2546 insertions, 0 deletions
diff --git a/widget/android/jni/Accessors.h b/widget/android/jni/Accessors.h new file mode 100644 index 000000000..fe2cbc9d4 --- /dev/null +++ b/widget/android/jni/Accessors.h @@ -0,0 +1,274 @@ +#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 +{ + Value(jboolean z) { val.z = z; } + Value(jbyte b) { val.b = b; } + Value(jchar c) { val.c = c; } + Value(jshort s) { val.s = s; } + Value(jint i) { val.i = i; } + Value(jlong j) { val.j = j; } + Value(jfloat f) { val.f = f; } + Value(jdouble d) { val.d = d; } + 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.RawClassRef(), 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.RawClassRef(), 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.RawClassRef(), 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.RawClassRef(), 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.RawClassRef(), 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; + + +// Define the sClassRef member declared in Refs.h and +// used by Method and Field above. +template<class C, typename T> jclass Context<C, T>::sClassRef; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Accessors_h__ diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h new file mode 100644 index 000000000..511d96a87 --- /dev/null +++ b/widget/android/jni/Natives.h @@ -0,0 +1,707 @@ +#ifndef mozilla_jni_Natives_h__ +#define mozilla_jni_Natives_h__ + +#include <jni.h> + +#include "mozilla/IndexSequence.h" +#include "mozilla/Move.h" +#include "mozilla/Tuple.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/jni/Accessors.h" +#include "mozilla/jni/Refs.h" +#include "mozilla/jni/Types.h" +#include "mozilla/jni/Utils.h" + +namespace mozilla { +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. To attach a Java instance to a C++ instance, pass in a pointer + * to the C++ class (i.e. MyClass*). + * + * class MyClass : public SupportsWeakPtr<MyClass> + * , public MyJavaClass::Natives<MyClass> + * { + * // ... + * + * public: + * MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MyClass) + * using MyJavaClass::Natives<MyClass>::Dispose; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives<MyClass>::AttachInstance(instance, this); + * + * // "instance" does NOT own "this", so the C++ object + * // lifetime is separate from the Java object lifetime. + * } + * }; + * + * * If the C++ class doesn't inherit from mozilla::SupportsWeakPtr, 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>::Dispose; + * + * static void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives<MyClass>::AttachInstance( + * instance, mozilla::MakeUnique<MyClass>()); + * + * // "instance" owns the newly created C++ object, so the C++ + * // object is destroyed as soon as instance.dispose() is called. + * } + * }; + */ + +namespace detail { + +inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) +{ + if (!handle) { + if (!env->ExceptionCheck()) { + ThrowException(env, "java/lang/NullPointerException", + "Null native pointer"); + } + return 0; + } + return handle; +} + +template<class Impl, bool UseWeakPtr = mozilla::IsBaseOf< + SupportsWeakPtr<Impl>, Impl>::value /* = false */> +struct NativePtr +{ + static Impl* Get(JNIEnv* env, jobject instance) + { + return reinterpret_cast<Impl*>(CheckNativeHandle( + env, GetNativeHandle(env, instance))); + } + + template<class LocalRef> + static Impl* Get(const LocalRef& instance) + { + return Get(instance.Env(), instance.Get()); + } + + 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()); + } + + template<class LocalRef> + static void Clear(const LocalRef& instance) + { + UniquePtr<Impl> ptr(reinterpret_cast<Impl*>( + 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 NativePtr<Impl, /* UseWeakPtr = */ true> +{ + static Impl* Get(JNIEnv* env, jobject instance) + { + const auto ptr = reinterpret_cast<WeakPtr<Impl>*>( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + if (!ptr) { + return nullptr; + } + + Impl* const impl = *ptr; + if (!impl) { + ThrowException(env, "java/lang/NullPointerException", + "Native object already released"); + } + return impl; + } + + template<class LocalRef> + static Impl* Get(const LocalRef& instance) + { + return Get(instance.Env(), instance.Get()); + } + + template<class LocalRef> + static void Set(const LocalRef& instance, Impl* ptr) + { + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), + reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr))); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template<class LocalRef> + static void Clear(const LocalRef& instance) + { + const auto ptr = reinterpret_cast<WeakPtr<Impl>*>( + 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 + +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(mozilla::Move(call)) {} + * virtual void run() override { mCall(); } + * }; + * + * public: + * template<class Functor> + * static void OnNativeCall(Functor&& call) + * { + * RunOnAnotherThread(new ProxyRunnable(mozilla::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(mozilla::IsPod<T>::value, "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 : public AbstractCall +{ + // "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. + typedef typename mozilla::Conditional<IsStatic, + Class, Owner>::Type ThisArgClass; + typedef typename mozilla::Conditional<IsStatic, + jclass, jobject>::Type ThisArgJNIType; + + // Type signature of the destination C++ function, which matches the + // Method template parameter in NativeStubImpl::Wrap. + typedef typename mozilla::Conditional<IsStatic, + typename mozilla::Conditional<HasThisArg, + void (*) (const Class::LocalRef&, Args...), + void (*) (Args...)>::Type, + typename mozilla::Conditional<HasThisArg, + void (Impl::*) (const typename Owner::LocalRef&, Args...), + void (Impl::*) (Args...)>::Type>::Type NativeCallType; + + // Destination C++ function. + NativeCallType mNativeCall; + // Saved this arg. + typename ThisArgClass::GlobalRef mThisArg; + // Saved arguments. + mozilla::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> + typename mozilla::EnableIf<Static && ThisArg, void>::Type + Call(const Class::LocalRef& cls, + mozilla::IndexSequence<Indices...>) const + { + (*mNativeCall)(cls, mozilla::Get<Indices>(mArgs)...); + } + + template<bool Static, bool ThisArg, size_t... Indices> + typename mozilla::EnableIf<Static && !ThisArg, void>::Type + Call(const Class::LocalRef& cls, + mozilla::IndexSequence<Indices...>) const + { + (*mNativeCall)(mozilla::Get<Indices>(mArgs)...); + } + + template<bool Static, bool ThisArg, size_t... Indices> + typename mozilla::EnableIf<!Static && ThisArg, void>::Type + Call(const typename Owner::LocalRef& inst, + mozilla::IndexSequence<Indices...>) const + { + Impl* const impl = NativePtr<Impl>::Get(inst); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...); + } + + template<bool Static, bool ThisArg, size_t... Indices> + typename mozilla::EnableIf<!Static && !ThisArg, void>::Type + Call(const typename Owner::LocalRef& inst, + mozilla::IndexSequence<Indices...>) const + { + Impl* const impl = NativePtr<Impl>::Get(inst); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...); + } + + template<size_t... Indices> + void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>) + { + int dummy[] = { + (ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)... + }; + mozilla::Unused << dummy; + } + +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; } + + // 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()() override + { + JNIEnv* const env = GetEnvForThread(); + typename ThisArgClass::LocalRef thisArg(env, mThisArg); + Call<IsStatic, HasThisArg>( + thisArg, typename IndexSequenceFor<Args...>::Type()); + + // 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, typename IndexSequenceFor<Args...>::Type()); + mThisArg.Clear(env); + } +}; + +template<class Impl, bool HasThisArg, typename... Args> +struct Dispatcher +{ + template<class Traits, bool IsStatic = Traits::isStatic, + typename... ProxyArgs> + static typename EnableIf< + Traits::dispatchTarget == DispatchTarget::PROXY, void>::Type + Run(ProxyArgs&&... args) + { + Impl::OnNativeCall(ProxyNativeCall< + Impl, typename Traits::Owner, IsStatic, + HasThisArg, Args...>(Forward<ProxyArgs>(args)...)); + } + + template<class Traits, bool IsStatic = Traits::isStatic, + typename ThisArg, typename... ProxyArgs> + static typename EnableIf< + Traits::dispatchTarget == DispatchTarget::GECKO, void>::Type + 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. + DispatchToGeckoThread(MakeUnique<ProxyNativeCall< + Impl, typename Traits::Owner, IsStatic, HasThisArg, + Args...>>(HasThisArg || !IsStatic ? thisArg : nullptr, + Forward<ProxyArgs>(args)...)); + } + + template<class Traits, bool IsStatic = false, typename... ProxyArgs> + static typename EnableIf< + Traits::dispatchTarget == DispatchTarget::CURRENT, void>::Type + Run(ProxyArgs&&... args) {} +}; + +} // 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 = mozilla::IsVoid<ReturnType>::value; + + struct VoidType { using JNIType = void; }; + using ReturnJNIType = typename Conditional< + isVoid, VoidType, TypeAdapter<ReturnType>>::Type::JNIType; + + using ReturnTypeForNonVoidInstance = typename Conditional< + !isStatic && !isVoid, ReturnType, VoidType>::Type; + using ReturnTypeForVoidInstance = typename Conditional< + !isStatic && isVoid, ReturnType, VoidType&>::Type; + using ReturnTypeForNonVoidStatic = typename Conditional< + isStatic && !isVoid, ReturnType, VoidType>::Type; + using ReturnTypeForVoidStatic = typename Conditional< + isStatic && isVoid, ReturnType, VoidType&>::Type; + + 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); + + Impl* const impl = NativePtr<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); + + Impl* const impl = NativePtr<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; + } + + Impl* const impl = NativePtr<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; + } + + Impl* const impl = NativePtr<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); + (Impl::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<Impl>* ptr) + { + static_assert(mozilla::IsBaseOf<SupportsWeakPtr<Impl>, Impl>::value, + "Attach with UniquePtr&& when not using WeakPtr"); + return NativePtr<Impl>::Set(instance, static_cast<Impl*>(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, + UniquePtr<Impl>&& ptr) + { + static_assert(!mozilla::IsBaseOf<SupportsWeakPtr<Impl>, Impl>::value, + "Attach with SupportsWeakPtr* when using WeakPtr"); + return NativePtr<Impl>::Set(instance, mozilla::Move(ptr)); + } + + // Get the C++ instance associated with a Java instance. + // There is always a pending exception if the return value is nullptr. + static Impl* GetNative(const typename Cls::LocalRef& instance) { + return NativePtr<Impl>::Get(instance); + } + + static void DisposeNative(const typename Cls::LocalRef& instance) { + NativePtr<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/Refs.h b/widget/android/jni/Refs.h new file mode 100644 index 000000000..9f54ee5cd --- /dev/null +++ b/widget/android/jni/Refs.h @@ -0,0 +1,953 @@ +#ifndef mozilla_jni_Refs_h__ +#define mozilla_jni_Refs_h__ + +#include <jni.h> + +#include "mozilla/Move.h" +#include "mozilla/jni/Utils.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 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; + + // 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; + } + + 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); + } + + // 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: + static jclass RawClassRef() + { + return sClassRef; + } + + 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; + } + + bool operator==(const Ref& other) const + { + // Treat two references of the same object as being the same. + return Ref::mInstance == other.mInstance || JNI_FALSE != + mEnv->IsSameObject(Ref::mInstance, other.mInstance); + } + + 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); + } +}; + + +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 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) + {} +}; + + +// Define bindings for built-in types. +using String = TypedObject<jstring>; +using Class = TypedObject<jclass>; +using Throwable = TypedObject<jthrowable>; + +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 + +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(mozilla::Move(ref)); + return swap(newRef); + } + + template<class C> + LocalRef<Cls>& operator=(GenericLocalRef<C>&& ref) + { + LocalRef<Cls> newRef(mozilla::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 DependentRef : public Cls::Ref +{ + using Ref = typename Cls::Ref; + +public: + 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()); + MOZ_CATCH_JNI_EXCEPTION(env); + return result; + } + +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 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 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> +{ + 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); + } + + 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 jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance())); + + nsTArray<ElementType> array((size_t(len))); + array.SetLength(size_t(len)); + (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)( + Base::Instance(), 0, len, + reinterpret_cast<JNIElemType*>(array.Elements())); + return array; + } + + ElementType operator[](size_t index) const + { + return GetElement(index); + } + + operator nsTArray<ElementType>() const + { + return GetElements(); + } +}; + +#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType) \ + template<> \ + class TypedObject<JNIType> : public ArrayRefBase<JNIType, ElementType> \ + { \ + public: \ + explicit TypedObject(const Context& ctx) \ + : ArrayRefBase<JNIType, ElementType>(ctx) \ + {} \ + } + +DEFINE_PRIMITIVE_ARRAY_REF(jbooleanArray, bool); +DEFINE_PRIMITIVE_ARRAY_REF(jbyteArray, int8_t); +DEFINE_PRIMITIVE_ARRAY_REF(jcharArray, char16_t); +DEFINE_PRIMITIVE_ARRAY_REF(jshortArray, int16_t); +DEFINE_PRIMITIVE_ARRAY_REF(jintArray, int32_t); +DEFINE_PRIMITIVE_ARRAY_REF(jlongArray, int64_t); +DEFINE_PRIMITIVE_ARRAY_REF(jfloatArray, float); +DEFINE_PRIMITIVE_ARRAY_REF(jdoubleArray, double); + +#undef DEFINE_PRIMITIVE_ARRAY_REF + + +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; + } + + 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<> +class TypedObject<jobjectArray> + : public ObjectBase<TypedObject<jobjectArray>, jobjectArray> +{ + using Base = ObjectBase<TypedObject<jobjectArray>, jobjectArray>; + +public: + 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 = mozilla::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 = mozilla::Move(objRef)); + } else if (clsRef) { + *globalRef = clsRef; + } + } +}; + +template<class Cls> +ReturnToGlobal<Cls> ReturnTo(GlobalRef<Cls>* ref) +{ + return ReturnToGlobal<Cls>(ref); +} + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Refs_h__ diff --git a/widget/android/jni/Types.h b/widget/android/jni/Types.h new file mode 100644 index 000000000..a083d3e50 --- /dev/null +++ b/widget/android/jni/Types.h @@ -0,0 +1,140 @@ +#ifndef mozilla_jni_Types_h__ +#define mozilla_jni_Types_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; + + +// 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 + +template<class Cls> constexpr jobject + (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Call)(jobject, jmethodID, jvalue*) MOZ_JNICALL_ABI; +template<class Cls> constexpr jobject + (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticCall)(jclass, jmethodID, jvalue*) 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> +{}; + + +#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 + +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 000000000..145f7e9ea --- /dev/null +++ b/widget/android/jni/Utils.cpp @@ -0,0 +1,301 @@ +#include "Utils.h" +#include "Types.h" + +#include <android/log.h> +#include <pthread.h> + +#include "mozilla/Assertions.h" + +#include "GeneratedJNIWrappers.h" +#include "nsAppShell.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +namespace mozilla { +namespace jni { + +namespace detail { + +#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName) \ + \ + constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Call) \ + (jobject, jmethodID, jvalue*) MOZ_JNICALL_ABI; \ + constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall) \ + (jclass, jmethodID, jvalue*) 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<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"; + + +JNIEnv* sGeckoThreadEnv; + +namespace { + +JavaVM* sJavaVM; +pthread_key_t sThreadEnvKey; +jclass sOOMErrorClass; +jobject sClassLoader; +jmethodID sClassLoaderLoadClass; +bool sIsFennec; + +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); + + auto geckoAppClass = Class::LocalRef::Adopt( + aEnv->FindClass("org/mozilla/gecko/GeckoApp")); + aEnv->ExceptionClear(); + sIsFennec = !!geckoAppClass; +} + +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; + +#ifdef MOZ_CRASHREPORTER + result &= NS_SUCCEEDED(CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("JavaStackTrace"), + String::Ref::From(aStack)->ToCString())); +#endif // MOZ_CRASHREPORTER + + 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)); + 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 && 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) { + return classRef.Forget(); + } + + __android_log_print( + ANDROID_LOG_ERROR, "Gecko", + ">>> FATAL JNI ERROR! FindClass(className=\"%s\") failed. " + "Did ProGuard optimize away something it shouldn't have?", + aClassName); + aEnv->ExceptionDescribe(); + MOZ_CRASH("Cannot find JNI class"); + return nullptr; +} + +void DispatchToGeckoThread(UniquePtr<AbstractCall>&& aCall) +{ + class AbstractCallEvent : public nsAppShell::Event + { + UniquePtr<AbstractCall> mCall; + + public: + AbstractCallEvent(UniquePtr<AbstractCall>&& aCall) + : mCall(Move(aCall)) + {} + + void Run() override + { + (*mCall)(); + } + }; + + nsAppShell::PostEvent(MakeUnique<AbstractCallEvent>(Move(aCall))); +} + +bool IsFennec() +{ + return sIsFennec; +} + +} // jni +} // mozilla diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h new file mode 100644 index 000000000..38e0b6b0c --- /dev/null +++ b/widget/android/jni/Utils.h @@ -0,0 +1,147 @@ +#ifndef mozilla_jni_Utils_h__ +#define mozilla_jni_Utils_h__ + +#include <jni.h> + +#include "mozilla/UniquePtr.h" + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) +#define MOZ_CHECK_JNI +#endif + +#ifdef MOZ_CHECK_JNI +#include <pthread.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. Method must + // return void. + GECKO, +}; + + +extern JNIEnv* sGeckoThreadEnv; + +inline bool IsAvailable() +{ + return !!sGeckoThreadEnv; +} + +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 = ::pthread_equal(::pthread_self(), \ + ::getJavaUiThread()); \ + 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); + +struct AbstractCall +{ + virtual ~AbstractCall() {} + virtual void operator()() = 0; +}; + +void DispatchToGeckoThread(UniquePtr<AbstractCall>&& aCall); + +/** + * Returns whether Gecko is running in a Fennec environment, as determined by + * the presence of the GeckoApp class. + */ +bool IsFennec(); + +} // jni +} // 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 000000000..31d7d32e6 --- /dev/null +++ b/widget/android/jni/moz.build @@ -0,0 +1,24 @@ +# -*- 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/. + +EXPORTS.mozilla.jni += [ + 'Accessors.h', + 'Natives.h', + 'Refs.h', + 'Types.h', + 'Utils.h', +] + +UNIFIED_SOURCES += [ + 'Utils.cpp', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/widget', + '/widget/android', +] |