Bug 1116868 - Add templated JNI classes; r=snorp
authorJim Chen <nchen@mozilla.com>
Fri, 09 Jan 2015 19:33:57 -0500
changeset 223153 69654c281aaac7e1724080995386a674847d9c8a
parent 223152 0f955b9395c8c1e48df9f1813fb9c91b6b53d4cf
child 223154 e706eac29b226646543cbab604a976b28352930a
push id10769
push usercbook@mozilla.com
push dateMon, 12 Jan 2015 14:15:52 +0000
treeherderfx-team@0e9765732906 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1116868
milestone37.0a1
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'