Bug 1116868 - Add templated JNI classes; r=snorp
authorJim Chen <nchen@mozilla.com>
Fri, 09 Jan 2015 19:33:57 -0500
changeset 248921 69654c281aaac7e1724080995386a674847d9c8a
parent 248920 0f955b9395c8c1e48df9f1813fb9c91b6b53d4cf
child 248922 e706eac29b226646543cbab604a976b28352930a
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1116868
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1116868 - Add templated JNI classes; r=snorp
widget/android/AndroidBridge.h
widget/android/jni/Accessors.h
widget/android/jni/Refs.h
widget/android/jni/Types.h
widget/android/jni/Utils.cpp
widget/android/jni/Utils.h
widget/android/jni/moz.build
widget/android/moz.build
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -23,29 +23,27 @@
 
 #include "nsIAndroidBridge.h"
 #include "nsIMobileMessageCallback.h"
 
 #include "mozilla/Likely.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Types.h"
+#include "mozilla/jni/Utils.h"
 
 // Some debug #defines
 // #define DEBUG_ANDROID_EVENTS
 // #define DEBUG_ANDROID_WIDGET
 
 class nsWindow;
 class nsIDOMMozSmsMessage;
 class nsIObserver;
 class Task;
 
-/* See the comment in AndroidBridge about this function before using it */
-extern "C" MOZ_EXPORT JNIEnv * GetJNIForThread();
-
 extern bool mozilla_AndroidBridge_SetMainThread(pthread_t);
 
 namespace base {
 class Thread;
 } // end namespace base
 
 typedef void* EGLSurface;
 
@@ -161,39 +159,26 @@ public:
     }
 
     static bool HasEnv() {
         return sBridge && sBridge->mJNIEnv;
     }
 
     static bool ThrowException(JNIEnv *aEnv, const char *aClass,
                                const char *aMessage) {
-        MOZ_ASSERT(aEnv, "Invalid thread JNI env");
-        jclass cls = aEnv->FindClass(aClass);
-        MOZ_ASSERT(cls, "Cannot find exception class");
-        bool ret = !aEnv->ThrowNew(cls, aMessage);
-        aEnv->DeleteLocalRef(cls);
-        return ret;
+
+        return jni::ThrowException(aEnv, aClass, aMessage);
     }
 
     static bool ThrowException(JNIEnv *aEnv, const char *aMessage) {
-        return ThrowException(aEnv, "java/lang/Exception", aMessage);
+        return jni::ThrowException(aEnv, aMessage);
     }
 
     static void HandleUncaughtException(JNIEnv *aEnv) {
-        MOZ_ASSERT(aEnv);
-        if (!aEnv->ExceptionCheck()) {
-            return;
-        }
-        jthrowable e = aEnv->ExceptionOccurred();
-        MOZ_ASSERT(e);
-        aEnv->ExceptionClear();
-        mozilla::widget::android::GeckoAppShell::HandleUncaughtException(nullptr, e);
-        // Should be dead by now...
-        MOZ_CRASH("Failed to handle uncaught exception");
+        jni::HandleUncaughtException(aEnv);
     }
 
     // The bridge needs to be constructed via ConstructBridge first,
     // and then once the Gecko main thread is spun up (Gecko side),
     // SetMainThread should be called which will create the JNIEnv for
     // us to use.  toolkit/xre/nsAndroidStartup.cpp calls
     // SetMainThread.
     bool SetMainThread(pthread_t thr);
@@ -552,17 +537,17 @@ public:
     }
 
     JNIEnv* GetEnv() {
         return mJNIEnv;
     }
 
     bool CheckForException() {
         if (mJNIEnv->ExceptionCheck()) {
-            AndroidBridge::HandleUncaughtException(mJNIEnv);
+            jni::HandleUncaughtException(mJNIEnv);
             return true;
         }
         return false;
     }
 
     // Note! Calling Purge makes all previous local refs created in
     // the AutoLocalJNIFrame's scope INVALID; be sure that you locked down
     // any local refs that you need to keep around in global refs!
new file mode 100644
--- /dev/null
+++ b/widget/android/jni/Accessors.h
@@ -0,0 +1,287 @@
+#ifndef mozilla_jni_Accessors_h__
+#define mozilla_jni_Accessors_h__
+
+#include <jni.h>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "AndroidBridge.h"
+
+namespace mozilla {
+namespace jni{
+
+namespace {
+
+// 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;
+};
+
+}
+
+// Base class for Method<>, Field<>, and Constructor<>.
+class Accessor {
+private:
+    template<class Cls>
+    static void EnsureClassRef(JNIEnv* env)
+    {
+        if (!Cls::sClassRef) {
+            MOZ_ALWAYS_TRUE(Cls::sClassRef =
+                AndroidBridge::GetClassGlobalRef(env, Cls::name));
+        }
+    }
+
+    static void GetNsresult(JNIEnv* env, nsresult* rv)
+    {
+        if (env->ExceptionCheck()) {
+            env->ExceptionClear();
+            *rv = NS_ERROR_FAILURE;
+        } else {
+            *rv = NS_OK;
+        }
+    }
+
+protected:
+    // Called before making a JNIEnv call.
+    template<class Traits>
+    static JNIEnv* BeginAccess()
+    {
+        JNIEnv* const env = Traits::isMultithreaded
+                ? GetJNIForThread() : AndroidBridge::GetJNIEnv();
+
+        EnsureClassRef<class Traits::Owner>(env);
+        return env;
+    }
+
+    // Called after making a JNIEnv call.
+    template<class Traits>
+    static void EndAccess(JNIEnv* env, nsresult* rv)
+    {
+        if (Traits::exceptionMode == ExceptionMode::ABORT) {
+            return HandleUncaughtException(env);
+
+        } else if (Traits::exceptionMode == ExceptionMode::NSRESULT) {
+            return GetNsresult(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 class Traits::Owner Owner;
+
+protected:
+    static jmethodID sID;
+
+    static JNIEnv* BeginAccess()
+    {
+        JNIEnv* const env = Base::BeginAccess<Traits>();
+
+        if (sID) {
+            return env;
+        }
+
+        if (Traits::isStatic) {
+            MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetStaticMethodID(
+                env, Traits::Owner::sClassRef, Traits::name, Traits::signature));
+        } else {
+            MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetMethodID(
+                env, Traits::Owner::sClassRef, Traits::name, Traits::signature));
+        }
+        return env;
+    }
+
+    static void EndAccess(JNIEnv* env, nsresult* rv)
+    {
+        return Base::EndAccess<Traits>(env, rv);
+    }
+
+public:
+    template<typename... Args>
+    static ReturnType Call(const Owner* cls, nsresult* rv, const Args&... args)
+    {
+        JNIEnv* const env = BeginAccess();
+
+        jvalue jargs[] = {
+            Value(TypeAdapter<Args>::FromNative(env, args)).val ...
+        };
+
+        auto result = TypeAdapter<ReturnType>::ToNative(env,
+                Traits::isStatic ?
+                (env->*TypeAdapter<ReturnType>::StaticCall)(
+                        Owner::sClassRef, sID, jargs) :
+                (env->*TypeAdapter<ReturnType>::Call)(
+                        cls->mInstance, sID, jargs));
+
+        EndAccess(env, 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 Owner;
+
+public:
+    template<typename... Args>
+    static void Call(const Owner* cls, nsresult* rv,
+                     const Args&... args) MOZ_OVERRIDE
+    {
+        JNIEnv* const env = Base::BeginAccess();
+
+        jvalue jargs[] = {
+            Value(TypeAdapter<Args>::FromNative(env, args)).val ...
+        };
+
+        if (Traits::isStatic) {
+            env->CallStaticVoidMethodA(Owner::sClassRef, Base::sID, jargs);
+        } else {
+            env->CallVoidMethodA(cls->mInstance, Base::sID, jargs);
+        }
+
+        Base::EndAccess(env, 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 class Traits::Owner Owner;
+    typedef typename Traits::ReturnType ReturnType;
+    typedef Method<Traits, ReturnType> Base;
+
+public:
+    template<typename... Args>
+    static ReturnType Call(const Owner* cls, nsresult* rv,
+                           const Args&... args) MOZ_OVERRIDE
+    {
+        JNIEnv* const env = Base::BeginAccess();
+
+        jvalue jargs[] = {
+            Value(TypeAdapter<Args>::FromNative(env, args)).val ...
+        };
+
+        auto result = TypeAdapter<ReturnType>::ToNative(
+                env, env->NewObjectA(Owner::sClassRef, Base::sID, jargs));
+
+        Base::EndAccess(env, 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 class Traits::Owner Owner;
+    typedef typename Traits::ReturnType GetterType;
+    typedef typename Traits::SetterType SetterType;
+
+    template<typename T> struct RemoveRef { typedef T Type; };
+    template<typename T> struct RemoveRef<const T&> { typedef T Type; };
+
+    // Setter type without any const/& added
+    typedef typename RemoveRef<SetterType>::Type SetterBaseType;
+
+private:
+
+    static jfieldID sID;
+
+    static JNIEnv* BeginAccess()
+    {
+        JNIEnv* const env = Base::BeginAccess<Traits>();
+
+        if (sID) {
+            return env;
+        }
+
+        if (Traits::isStatic) {
+            MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetStaticFieldID(
+                env, Traits::Owner::sClassRef, Traits::name, Traits::signature));
+        } else {
+            MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetFieldID(
+                env, Traits::Owner::sClassRef, Traits::name, Traits::signature));
+        }
+        return env;
+    }
+
+    static void EndAccess(JNIEnv* env, nsresult* rv)
+    {
+        return Base::EndAccess<Traits>(env, rv);
+    }
+
+public:
+    static GetterType Get(const Owner* cls, nsresult* rv)
+    {
+        JNIEnv* const env = BeginAccess();
+
+        auto result = TypeAdapter<GetterType>::ToNative(
+                env, Traits::isStatic ?
+
+                (env->*TypeAdapter<GetterType>::StaticGet)
+                        (Owner::sClassRef, sID) :
+
+                (env->*TypeAdapter<GetterType>::Get)
+                        (cls->mInstance, sID));
+
+        EndAccess(env, rv);
+        return result;
+    }
+
+    static void Set(const Owner* cls, nsresult* rv, SetterType val)
+    {
+        JNIEnv* const env = BeginAccess();
+
+        if (Traits::isStatic) {
+            (env->*TypeAdapter<SetterBaseType>::StaticSet)(
+                    Owner::sClassRef, sID,
+                    TypeAdapter<SetterBaseType>::FromNative(env, val));
+        } else {
+            (env->*TypeAdapter<SetterBaseType>::Set)(
+                    cls->mInstance, sID,
+                    TypeAdapter<SetterBaseType>::FromNative(env, val));
+        }
+
+        EndAccess(env, 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> jclass Class<C>::sClassRef;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Accessors_h__
new file mode 100644
--- /dev/null
+++ b/widget/android/jni/Refs.h
@@ -0,0 +1,644 @@
+#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"
+
+namespace mozilla {
+namespace jni {
+
+class Accessor;
+template<class T> class Constructor;
+template<class T> class Field;
+template<class T, typename R> class Method;
+
+// Wrapped object reference (e.g. jobject, jclass, etc...)
+template<class Cls> class Ref;
+// Wrapped local reference that inherits from Ref.
+template<class Cls> class LocalRef;
+// Wrapped global reference that inherits from Ref.
+template<class Cls> class GlobalRef;
+
+// Type used for a reference parameter. Default is a wrapped object
+// reference, but Param can be specialized to define custom behavior,
+// e.g. a StringParam class that automatically converts nsAString& and
+// nsACString& to a jstring.
+template<class Cls> struct Param { typedef Ref<Cls> Type; };
+
+
+// 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,
+};
+
+
+// Base class for all JNI binding classes.
+// Templated so that we have one sClassRef for each class.
+template<class Cls>
+class Class
+{
+    friend class Accessor;
+    template<class T> friend class Constructor;
+    template<class T> friend class Field;
+    template<class T, typename R> friend class Method;
+
+private:
+    static jclass sClassRef; // global reference
+
+protected:
+    jobject mInstance; // local or global reference
+
+    Class(jobject instance) : mInstance(instance) {}
+};
+
+
+// Binding for a plain jobject.
+class Object : public Class<Object>
+{
+protected:
+    Object(jobject instance) : Class(instance) {}
+
+public:
+    typedef jni::Ref<Object> Ref;
+    typedef jni::LocalRef<Object>  LocalRef;
+    typedef jni::GlobalRef<Object> GlobalRef;
+    typedef const typename jni::Param<Object>::Type& Param;
+};
+
+
+// Binding for a built-in object reference other than jobject.
+template<typename T>
+class TypedObject : public Object
+{
+    typedef TypedObject<T> Self;
+
+protected:
+    TypedObject(jobject instance) : Object(instance) {}
+
+public:
+    typedef jni::Ref<Self> Ref;
+    typedef jni::LocalRef<Self>  LocalRef;
+    typedef jni::GlobalRef<Self> GlobalRef;
+    typedef const typename jni::Param<Self>::Type& Param;
+};
+
+// Define bindings for built-in types.
+typedef TypedObject<jstring>    String;
+typedef TypedObject<jclass>     ClassObject;
+typedef TypedObject<jthrowable> Throwable;
+
+typedef TypedObject<jbooleanArray> BooleanArray;
+typedef TypedObject<jbyteArray>    ByteArray;
+typedef TypedObject<jcharArray>    CharArray;
+typedef TypedObject<jshortArray>   ShortArray;
+typedef TypedObject<jintArray>     IntArray;
+typedef TypedObject<jlongArray>    LongArray;
+typedef TypedObject<jfloatArray>   FloatArray;
+typedef TypedObject<jdoubleArray>  DoubleArray;
+typedef TypedObject<jobjectArray>  ObjectArray;
+
+template<> struct Param<String> { class Type; };
+
+
+// Base class for Ref and its specializations.
+template<class Cls, typename JNIType>
+class RefBase : protected Cls
+{
+    typedef RefBase<Cls, JNIType> Self;
+    typedef void (Self::*bool_type)() const;
+    void non_null_reference() const {}
+
+protected:
+    RefBase(jobject instance) : Cls(instance) {}
+
+public:
+    // Construct a Ref form a raw JNI reference.
+    static Ref<Cls> From(JNIType obj)
+    {
+        return Ref<Cls>(static_cast<jobject>(obj));
+    }
+
+    // Get the raw JNI reference.
+    JNIType Get() const
+    {
+        return static_cast<JNIType>(Cls::mInstance);
+    }
+
+    bool operator==(const RefBase& other) const
+    {
+        // Treat two references of the same object as being the same.
+        return Cls::mInstance == other.mInstance &&
+                GetJNIForThread()->IsSameObject(
+                        Cls::mInstance, other.mInstance) != JNI_FALSE;
+    }
+
+    bool operator!=(const RefBase& other) const
+    {
+        return !operator==(other);
+    }
+
+    bool operator==(decltype(nullptr)) const
+    {
+        return !Cls::mInstance;
+    }
+
+    bool operator!=(decltype(nullptr)) const
+    {
+        return !!Cls::mInstance;
+    }
+
+    Cls* operator->()
+    {
+        MOZ_ASSERT(Cls::mInstance);
+        return this;
+    }
+
+    const Cls* operator->() const
+    {
+        MOZ_ASSERT(Cls::mInstance);
+        return this;
+    }
+
+    // Any ref can be cast to an object ref.
+    operator Ref<Object>() const;
+
+    // Null checking (e.g. !!ref) using the safe-bool idiom.
+    operator bool_type() const
+    {
+        return Cls::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;
+};
+
+
+// Wrapped object reference (e.g. jobject, jclass, etc...)
+template<class Cls>
+class Ref : public RefBase<Cls, jobject>
+{
+    template<class C, typename T> friend class RefBase;
+    friend class jni::LocalRef<Cls>;
+    friend class jni::GlobalRef<Cls>;
+
+    typedef RefBase<Cls, jobject> Base;
+
+protected:
+    // 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.
+    Ref(jobject instance) : Base(instance) {}
+
+    // Protected 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& ref) : Base(ref.mInstance) {}
+
+public:
+    MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {}
+};
+
+
+template<class Cls, typename JNIType>
+RefBase<Cls, JNIType>::operator Ref<Object>() const
+{
+    return Ref<Object>(Cls::mInstance);
+}
+
+
+template<typename T>
+class Ref<TypedObject<T>>
+        : public RefBase<TypedObject<T>, T>
+{
+    friend class RefBase<TypedObject<T>, T>;
+    friend class jni::LocalRef<TypedObject<T>>;
+    friend class jni::GlobalRef<TypedObject<T>>;
+
+    typedef RefBase<TypedObject<T>, T> Base;
+
+protected:
+    Ref(jobject instance) : Base(instance) {}
+
+    Ref(const Ref& ref) : Base(ref.mInstance) {}
+
+public:
+    MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {}
+};
+
+
+namespace {
+
+// See explanation in LocalRef.
+template<class Cls> struct GenericObject { typedef Object Type; };
+template<> struct GenericObject<Object> { typedef struct {} Type; };
+
+} // namespace
+
+template<class Cls>
+class LocalRef : public Ref<Cls>
+{
+    template<class C> friend class LocalRef;
+
+private:
+    // 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>.
+    typedef typename GenericObject<Cls>::Type GenericObject;
+
+    JNIEnv* const mEnv;
+
+    LocalRef(JNIEnv* env, jobject instance)
+        : Ref<Cls>(instance)
+        , mEnv(env)
+    {}
+
+    LocalRef& swap(LocalRef& other)
+    {
+        auto instance = other.mInstance;
+        other.mInstance = Ref<Cls>::mInstance;
+        Ref<Cls>::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(jobject instance)
+    {
+        return LocalRef(GetJNIForThread(), instance);
+    }
+
+    static LocalRef Adopt(JNIEnv* env, jobject instance)
+    {
+        return LocalRef(env, instance);
+    }
+
+    // Copy constructor.
+    LocalRef(const LocalRef<Cls>& ref)
+        : Ref<Cls>(ref.mEnv->NewLocalRef(ref.mInstance))
+        , mEnv(ref.mEnv)
+    {}
+
+    // Move constructor.
+    LocalRef(LocalRef<Cls>&& ref)
+        : Ref<Cls>(ref.mInstance)
+        , mEnv(ref.mEnv)
+    {
+        ref.mInstance = nullptr;
+    }
+
+    explicit LocalRef(JNIEnv* env = GetJNIForThread())
+        : Ref<Cls>(nullptr)
+        , mEnv(env)
+    {}
+
+    // Construct a LocalRef from any Ref,
+    // which means creating a new local reference.
+    MOZ_IMPLICIT LocalRef(const Ref<Cls>& ref)
+        : Ref<Cls>(nullptr)
+        , mEnv(GetJNIForThread())
+    {
+        Ref<Cls>::mInstance = mEnv->NewLocalRef(ref.mInstance);
+    }
+
+    // Move a LocalRef<Object> into a LocalRef<Cls> without
+    // creating/deleting local references.
+    MOZ_IMPLICIT LocalRef(LocalRef<GenericObject>&& ref)
+        : Ref<Cls>(ref.mInstance)
+        , mEnv(ref.mEnv)
+    {
+        ref.mInstance = nullptr;
+    }
+
+    // Implicitly converts nullptr to LocalRef.
+    MOZ_IMPLICIT LocalRef(decltype(nullptr))
+        : Ref<Cls>(nullptr)
+        , mEnv(GetJNIForThread())
+    {}
+
+    ~LocalRef()
+    {
+        if (Ref<Cls>::mInstance) {
+            mEnv->DeleteLocalRef(Ref<Cls>::mInstance);
+            Ref<Cls>::mInstance = nullptr;
+        }
+    }
+
+    // Get the JNIEnv* associated with this local reference.
+    JNIEnv* Env() const
+    {
+        return mEnv;
+    }
+
+    // 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.
+    auto Forget() -> decltype(Ref<Cls>(nullptr).Get())
+    {
+        const auto obj = Ref<Cls>::Get();
+        Ref<Cls>::mInstance = nullptr;
+        return obj;
+    }
+
+    LocalRef<Cls>& operator=(LocalRef<Cls> ref)
+    {
+        return swap(ref);
+    }
+
+    LocalRef<Cls>& operator=(const Ref<Cls>& ref)
+    {
+        LocalRef<Cls> newRef(mEnv, ref.mInstance);
+        return swap(newRef);
+    }
+
+    LocalRef<Cls>& operator=(LocalRef<GenericObject>&& ref)
+    {
+        LocalRef<Cls> newRef(mozilla::Forward<LocalRef<GenericObject>>(ref));
+        return swap(newRef);
+    }
+
+    LocalRef<Cls>& operator=(decltype(nullptr))
+    {
+        LocalRef<Cls> newRef(mEnv, nullptr);
+        return swap(newRef);
+    }
+};
+
+
+template<class Cls>
+class GlobalRef : public Ref<Cls>
+{
+private:
+    static jobject NewGlobalRef(JNIEnv* env, jobject instance)
+    {
+        if (!instance) {
+            return nullptr;
+        }
+        if (!env) {
+            env = GetJNIForThread();
+        }
+        return env->NewGlobalRef(instance);
+    }
+
+    GlobalRef& swap(GlobalRef& other)
+    {
+        auto instance = other.mInstance;
+        other.mInstance = Ref<Cls>::mInstance;
+        Ref<Cls>::mInstance = instance;
+        return *this;
+    }
+
+public:
+    GlobalRef()
+        : Ref<Cls>(nullptr)
+    {}
+
+    // Copy constructor
+    GlobalRef(const GlobalRef& ref)
+        : Ref<Cls>(ref.mInstance)
+    {}
+
+    // Move constructor
+    GlobalRef(GlobalRef&& ref)
+        : Ref<Cls>(ref.mInstance)
+    {
+        ref.mInstance = nullptr;
+    }
+
+    MOZ_IMPLICIT GlobalRef(const Ref<Cls>& ref)
+        : Ref<Cls>(NewGlobalRef(nullptr, ref.mInstance))
+    {}
+
+    GlobalRef(JNIEnv* env, const Ref<Cls>& ref)
+        : Ref<Cls>(NewGlobalRef(env, ref.mInstance))
+    {}
+
+    // Implicitly converts nullptr to GlobalRef.
+    MOZ_IMPLICIT GlobalRef(decltype(nullptr))
+        : Ref<Cls>(nullptr)
+    {}
+
+    ~GlobalRef()
+    {
+        if (Ref<Cls>::mInstance) {
+            JNIEnv* const env = GetJNIForThread();
+            env->DeleteGlobalRef(Ref<Cls>::mInstance);
+            Ref<Cls>::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.
+    auto Forget() -> decltype(Ref<Cls>(nullptr).Get())
+    {
+        const auto obj = Ref<Cls>::Get();
+        Ref<Cls>::mInstance = nullptr;
+        return obj;
+    }
+
+    GlobalRef<Cls>& operator=(GlobalRef<Cls> ref)
+    {
+        return swap(ref);
+    }
+
+    GlobalRef<Cls>& operator=(const Ref<Cls>& ref)
+    {
+        GlobalRef<Cls> newRef(ref);
+        return swap(newRef);
+    }
+
+    GlobalRef<Cls>& operator=(decltype(nullptr))
+    {
+        GlobalRef<Cls> newRef(nullptr);
+        return swap(newRef);
+    }
+};
+
+
+// Ref specialization for jstring.
+template<>
+class Ref<String> : public RefBase<String, jstring>
+{
+    friend class RefBase<String, jstring>;
+    friend class jni::LocalRef<String>;
+    friend class jni::GlobalRef<String>;
+
+    typedef RefBase<TypedObject<jstring>, jstring> Base;
+
+protected:
+    Ref(jobject instance) : Base(instance) {}
+
+    Ref(const Ref& ref) : Base(ref.mInstance) {}
+
+public:
+    MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {}
+
+    // Get the length of the jstring.
+    size_t Length() const
+    {
+        JNIEnv* const env = GetJNIForThread();
+        return env->GetStringLength(Get());
+    }
+
+    // Convert jstring to a nsString.
+    operator nsString() const
+    {
+        MOZ_ASSERT(Object::mInstance);
+
+        JNIEnv* const env = GetJNIForThread();
+        const jchar* const str = env->GetStringChars(Get(), nullptr);
+        const jsize len = env->GetStringLength(Get());
+
+        nsString result(reinterpret_cast<const char16_t*>(str), len);
+        env->ReleaseStringChars(Get(), str);
+        return result;
+    }
+
+    // Convert jstring to a nsCString.
+    operator nsCString() const
+    {
+        return NS_ConvertUTF16toUTF8(operator nsString());
+    }
+};
+
+
+// Define a custom parameter type for String,
+// which accepts both String::Ref and nsAString/nsACString
+class Param<String>::Type : public Ref<String>
+{
+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());
+        HandleUncaughtException(env);
+        return result;
+    }
+
+public:
+    MOZ_IMPLICIT Type(const String::Ref& ref)
+        : Ref<String>(ref.Get())
+        , mEnv(nullptr)
+    {}
+
+    MOZ_IMPLICIT Type(const nsAString& str, JNIEnv* env = GetJNIForThread())
+        : Ref<String>(GetString(env, str))
+        , mEnv(env)
+    {}
+
+    MOZ_IMPLICIT Type(const char16_t* str, JNIEnv* env = GetJNIForThread())
+        : Ref<String>(GetString(env, nsDependentString(str)))
+        , mEnv(env)
+    {}
+
+    MOZ_IMPLICIT Type(const nsACString& str, JNIEnv* env = GetJNIForThread())
+        : Ref<String>(GetString(env, NS_ConvertUTF8toUTF16(str)))
+        , mEnv(env)
+    {}
+
+    MOZ_IMPLICIT Type(const char* str, JNIEnv* env = GetJNIForThread())
+        : Ref<String>(GetString(env, NS_ConvertUTF8toUTF16(str)))
+        , mEnv(env)
+    {}
+
+    ~Type()
+    {
+        if (mEnv) {
+            mEnv->DeleteLocalRef(Get());
+        }
+    }
+};
+
+
+// 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__
new file mode 100644
--- /dev/null
+++ b/widget/android/jni/Types.h
@@ -0,0 +1,120 @@
+#ifndef mozilla_jni_Types_h__
+#define mozilla_jni_Types_h__
+
+#include <jni.h>
+
+#include "mozilla/jni/Refs.h"
+#include "AndroidBridge.h"
+
+namespace mozilla {
+namespace jni {
+namespace {
+
+// TypeAdapter specializations are the interfaces between naive (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>> {
+    static constexpr auto Call = &JNIEnv::CallObjectMethodA;
+    static constexpr auto StaticCall = &JNIEnv::CallStaticObjectMethodA;
+    static constexpr auto Get = &JNIEnv::GetObjectField;
+    static constexpr auto StaticGet = &JNIEnv::GetStaticObjectField;
+
+    static LocalRef<Cls> ToNative(JNIEnv* env, jobject instance) {
+        return LocalRef<Cls>::Adopt(env, instance);
+    }
+};
+
+template<class Cls> constexpr jobject
+    (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Call)(jobject, jmethodID, jvalue*);
+template<class Cls> constexpr jobject
+    (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticCall)(jclass, jmethodID, jvalue*);
+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> struct TypeAdapter<Ref<Cls>> {
+    static constexpr auto Set = &JNIEnv::SetObjectField;
+    static constexpr auto StaticSet = &JNIEnv::SetStaticObjectField;
+
+    static jobject FromNative(JNIEnv*, const Ref<Cls>& instance) {
+        return instance.Get();
+    }
+};
+
+template<class Cls> constexpr void
+    (JNIEnv::*TypeAdapter<Ref<Cls>>::Set)(jobject, jfieldID, jobject);
+template<class Cls> constexpr void
+    (JNIEnv::*TypeAdapter<Ref<Cls>>::StaticSet)(jclass, jfieldID, jobject);
+
+
+// jstring has its own Param type.
+template<> struct TypeAdapter<class Param<String>::Type>
+        : public TypeAdapter<String::Ref>
+{};
+
+
+#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName) \
+    \
+    template<> struct TypeAdapter<NativeType> { \
+        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 JNIType FromNative(JNIEnv*, NativeType val) { \
+            return static_cast<JNIType>(val); \
+        } \
+        static NativeType ToNative(JNIEnv*, JNIType val) { \
+            return static_cast<NativeType>(val); \
+        } \
+    }; \
+    \
+    constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Call) \
+            (jobject, jmethodID, jvalue*); \
+    constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall) \
+            (jclass, jmethodID, jvalue*); \
+    constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Get) \
+            (jobject, jfieldID); \
+    constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticGet) \
+            (jclass, jfieldID); \
+    constexpr void (JNIEnv::*TypeAdapter<NativeType>::Set) \
+            (jobject, jfieldID, JNIType); \
+    constexpr void (JNIEnv::*TypeAdapter<NativeType>::StaticSet) \
+            (jclass, jfieldID, JNIType)
+
+
+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
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Types_h__
new file mode 100644
--- /dev/null
+++ b/widget/android/jni/Utils.cpp
@@ -0,0 +1,43 @@
+#include "Utils.h"
+
+#include "mozilla/Assertions.h"
+
+#include "GeneratedJNIWrappers.h"
+#include "Refs.h"
+
+namespace mozilla {
+namespace jni {
+
+bool ThrowException(JNIEnv *aEnv, const char *aClass,
+                    const char *aMessage)
+{
+    MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+
+    ClassObject::LocalRef cls =
+            ClassObject::LocalRef::Adopt(aEnv->FindClass(aClass));
+    MOZ_ASSERT(cls, "Cannot find exception class");
+
+    return !aEnv->ThrowNew(cls.Get(), aMessage);
+}
+
+void HandleUncaughtException(JNIEnv *aEnv)
+{
+    MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+
+    if (!aEnv->ExceptionCheck()) {
+        return;
+    }
+
+    Throwable::LocalRef e =
+            Throwable::LocalRef::Adopt(aEnv->ExceptionOccurred());
+    MOZ_ASSERT(e);
+
+    aEnv->ExceptionClear();
+    widget::GeckoAppShell::HandleUncaughtException(nullptr, e);
+
+    // Should be dead by now...
+    MOZ_CRASH("Failed to handle uncaught exception");
+}
+
+} // jni
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/widget/android/jni/Utils.h
@@ -0,0 +1,27 @@
+#ifndef mozilla_jni_Utils_h__
+#define mozilla_jni_Utils_h__
+
+#include <jni.h>
+
+#include "mozilla/Types.h"
+
+/* See the comment in AndroidBridge about this function before using it */
+extern "C" MOZ_EXPORT JNIEnv * GetJNIForThread();
+
+namespace mozilla {
+namespace jni {
+
+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);
+}
+
+void HandleUncaughtException(JNIEnv *aEnv);
+
+} // jni
+} // mozilla
+
+#endif // mozilla_jni_Utils_h__
new file mode 100644
--- /dev/null
+++ b/widget/android/jni/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; 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',
+    'Refs.h',
+    'Types.h',
+    'Utils.h',
+]
+
+SOURCES += [
+    'Utils.cpp',
+]
+
+FAIL_ON_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/widget/android',
+]
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; 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/.
 
 DIRS += [
     'bindings',
+    'jni',
 ]
 
 XPIDL_SOURCES += [
     'nsIAndroidBridge.idl',
 ]
 
 XPIDL_MODULE = 'widget_android'