widget/android/jni/Accessors.h
author Doug Thayer <dothayer@mozilla.com>
Fri, 01 Mar 2019 18:29:09 +0000
changeset 520116 8b3fe0426ffc1b3a2ad044ef6cdde6c4f736f8e2
parent 505383 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1442694 - Fix failures due to removing selected tab r=Gijs This adds test which reproduce the failure as well as the fix. Essentially, if we hit the edited case in SessionStore with `tab` equal to `tabbrowser.tabs[t]`, we remove the tab and then try to pin it, which obviously blows up. Note: the additional method in SessionStore.jsm was largely to get around complexity requirements inside restoreWindow. Cleaner solutions welcome. Differential Revision: https://phabricator.services.mozilla.com/D21383

#ifndef mozilla_jni_Accessors_h__
#define mozilla_jni_Accessors_h__

#include <jni.h>

#include "mozilla/jni/Refs.h"
#include "mozilla/jni/Types.h"
#include "mozilla/jni/Utils.h"
#include "AndroidBridge.h"

namespace mozilla {
namespace jni {

namespace detail {

// Helper class to convert an arbitrary type to a jvalue, e.g. Value(123).val.
struct Value {
  explicit Value(jboolean z) { val.z = z; }
  explicit Value(jbyte b) { val.b = b; }
  explicit Value(jchar c) { val.c = c; }
  explicit Value(jshort s) { val.s = s; }
  explicit Value(jint i) { val.i = i; }
  explicit Value(jlong j) { val.j = j; }
  explicit Value(jfloat f) { val.f = f; }
  explicit Value(jdouble d) { val.d = d; }
  explicit Value(jobject l) { val.l = l; }

  jvalue val;
};

}  // namespace detail

using namespace detail;

// Base class for Method<>, Field<>, and Constructor<>.
class Accessor {
  static void GetNsresult(JNIEnv* env, nsresult* rv) {
    if (env->ExceptionCheck()) {
#ifdef MOZ_CHECK_JNI
      env->ExceptionDescribe();
#endif
      env->ExceptionClear();
      *rv = NS_ERROR_FAILURE;
    } else {
      *rv = NS_OK;
    }
  }

 protected:
  // Called after making a JNIEnv call.
  template <class Traits>
  static void EndAccess(const typename Traits::Owner::Context& ctx,
                        nsresult* rv) {
    if (Traits::exceptionMode == ExceptionMode::ABORT) {
      MOZ_CATCH_JNI_EXCEPTION(ctx.Env());

    } else if (Traits::exceptionMode == ExceptionMode::NSRESULT) {
      GetNsresult(ctx.Env(), rv);
    }
  }
};

// Member<> is used to call a JNI method given a traits class.
template <class Traits, typename ReturnType = typename Traits::ReturnType>
class Method : public Accessor {
  typedef Accessor Base;
  typedef typename Traits::Owner::Context Context;

 protected:
  static jmethodID sID;

  static void BeginAccess(const Context& ctx) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
    static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT,
                  "Dispatching not supported for method call");

    if (sID) {
      return;
    }

    if (Traits::isStatic) {
      MOZ_ALWAYS_TRUE(
          sID = AndroidBridge::GetStaticMethodID(
              ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature));
    } else {
      MOZ_ALWAYS_TRUE(
          sID = AndroidBridge::GetMethodID(ctx.Env(), ctx.ClassRef(),
                                           Traits::name, Traits::signature));
    }
  }

  static void EndAccess(const Context& ctx, nsresult* rv) {
    return Base::EndAccess<Traits>(ctx, rv);
  }

 public:
  template <typename... Args>
  static ReturnType Call(const Context& ctx, nsresult* rv,
                         const Args&... args) {
    JNIEnv* const env = ctx.Env();
    BeginAccess(ctx);

    jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};

    auto result = TypeAdapter<ReturnType>::ToNative(
        env, Traits::isStatic ? (env->*TypeAdapter<ReturnType>::StaticCall)(
                                    ctx.ClassRef(), sID, jargs)
                              : (env->*TypeAdapter<ReturnType>::Call)(
                                    ctx.Get(), sID, jargs));

    EndAccess(ctx, rv);
    return result;
  }
};

// Define sID member.
template <class T, typename R>
jmethodID Method<T, R>::sID;

// Specialize void because C++ forbids us from
// using a "void" temporary result variable.
template <class Traits>
class Method<Traits, void> : public Method<Traits, bool> {
  typedef Method<Traits, bool> Base;
  typedef typename Traits::Owner::Context Context;

 public:
  template <typename... Args>
  static void Call(const Context& ctx, nsresult* rv, const Args&... args) {
    JNIEnv* const env = ctx.Env();
    Base::BeginAccess(ctx);

    jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};

    if (Traits::isStatic) {
      env->CallStaticVoidMethodA(ctx.ClassRef(), Base::sID, jargs);
    } else {
      env->CallVoidMethodA(ctx.Get(), Base::sID, jargs);
    }

    Base::EndAccess(ctx, rv);
  }
};

// Constructor<> is used to construct a JNI instance given a traits class.
template <class Traits>
class Constructor : protected Method<Traits, typename Traits::ReturnType> {
  typedef typename Traits::Owner::Context Context;
  typedef typename Traits::ReturnType ReturnType;
  typedef Method<Traits, ReturnType> Base;

 public:
  template <typename... Args>
  static ReturnType Call(const Context& ctx, nsresult* rv,
                         const Args&... args) {
    JNIEnv* const env = ctx.Env();
    Base::BeginAccess(ctx);

    jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...};

    auto result = TypeAdapter<ReturnType>::ToNative(
        env, env->NewObjectA(ctx.ClassRef(), Base::sID, jargs));

    Base::EndAccess(ctx, rv);
    return result;
  }
};

// Field<> is used to access a JNI field given a traits class.
template <class Traits>
class Field : public Accessor {
  typedef Accessor Base;
  typedef typename Traits::Owner::Context Context;
  typedef typename Traits::ReturnType GetterType;
  typedef typename Traits::SetterType SetterType;

 private:
  static jfieldID sID;

  static void BeginAccess(const Context& ctx) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);
    static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT,
                  "Dispatching not supported for field access");

    if (sID) {
      return;
    }

    if (Traits::isStatic) {
      MOZ_ALWAYS_TRUE(
          sID = AndroidBridge::GetStaticFieldID(
              ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature));
    } else {
      MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetFieldID(ctx.Env(), ctx.ClassRef(),
                                                      Traits::name,
                                                      Traits::signature));
    }
  }

  static void EndAccess(const Context& ctx, nsresult* rv) {
    return Base::EndAccess<Traits>(ctx, rv);
  }

 public:
  static GetterType Get(const Context& ctx, nsresult* rv) {
    JNIEnv* const env = ctx.Env();
    BeginAccess(ctx);

    auto result = TypeAdapter<GetterType>::ToNative(
        env, Traits::isStatic
                 ?

                 (env->*TypeAdapter<GetterType>::StaticGet)(ctx.ClassRef(), sID)
                 :

                 (env->*TypeAdapter<GetterType>::Get)(ctx.Get(), sID));

    EndAccess(ctx, rv);
    return result;
  }

  static void Set(const Context& ctx, nsresult* rv, SetterType val) {
    JNIEnv* const env = ctx.Env();
    BeginAccess(ctx);

    if (Traits::isStatic) {
      (env->*TypeAdapter<SetterType>::StaticSet)(
          ctx.ClassRef(), sID, TypeAdapter<SetterType>::FromNative(env, val));
    } else {
      (env->*TypeAdapter<SetterType>::Set)(
          ctx.Get(), sID, TypeAdapter<SetterType>::FromNative(env, val));
    }

    EndAccess(ctx, rv);
  }
};

// Define sID member.
template <class T>
jfieldID Field<T>::sID;

}  // namespace jni
}  // namespace mozilla

#endif  // mozilla_jni_Accessors_h__