/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_jni_Natives_h__
#define mozilla_jni_Natives_h__

#include <jni.h>
#include <tuple>
#include <type_traits>
#include <utility>

#include "mozilla/RefPtr.h"
#include "mozilla/RWLock.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/jni/Accessors.h"
#include "mozilla/jni/Refs.h"
#include "mozilla/jni/Types.h"
#include "mozilla/jni/Utils.h"
#include "nsThreadUtils.h"

#if defined(_MSC_VER)  // MSVC
#  define FUNCTION_SIGNATURE __FUNCSIG__
#elif defined(__GNUC__)  // GCC, Clang
#  define FUNCTION_SIGNATURE __PRETTY_FUNCTION__
#endif

struct NativeException {
  const char* str;
};

template <class T>
static NativeException NullHandle() {
  return {FUNCTION_SIGNATURE};
}

template <class T>
static NativeException NullWeakPtr() {
  return {FUNCTION_SIGNATURE};
}

namespace mozilla {

template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
class MozPromise;

namespace jni {

/**
 * C++ classes implementing instance (non-static) native methods can choose
 * from one of two ownership models, when associating a C++ object with a Java
 * instance.
 *
 * * If the C++ class inherits from mozilla::SupportsWeakPtr, weak pointers
 *   will be used. The Java instance will store and own the pointer to a
 *   WeakPtr object. The C++ class itself is otherwise not owned or directly
 *   referenced. Note that mozilla::SupportsWeakPtr only supports being used on
 *   a single thread. To attach a Java instance to a C++ instance, pass in a
 *   mozilla::SupportsWeakPtr pointer to the C++ class (i.e. MyClass*).
 *
 *   class MyClass : public SupportsWeakPtr
 *                 , public MyJavaClass::Natives<MyClass>
 *   {
 *       // ...
 *
 *   public:
 *       using MyJavaClass::Natives<MyClass>::DisposeNative;
 *
 *       void AttachTo(const MyJavaClass::LocalRef& instance)
 *       {
 *           MyJavaClass::Natives<MyClass>::AttachNative(
 *                   instance, static_cast<SupportsWeakPtr*>(this));
 *
 *           // "instance" does NOT own "this", so the C++ object
 *           // lifetime is separate from the Java object lifetime.
 *       }
 *   };
 *
 * * If the C++ class contains public members AddRef() and Release(), the Java
 *   instance will store and own the pointer to a RefPtr object, which holds a
 *   strong reference on the C++ instance. Normal ref-counting considerations
 *   apply in this case; for example, disposing may cause the C++ instance to
 *   be deleted and the destructor to be run on the current thread, which may
 *   not be desirable. To attach a Java instance to a C++ instance, pass in a
 *   pointer to the C++ class (i.e. MyClass*).
 *
 *   class MyClass : public RefCounted<MyClass>
 *                 , public MyJavaClass::Natives<MyClass>
 *   {
 *       // ...
 *
 *   public:
 *       using MyJavaClass::Natives<MyClass>::DisposeNative;
 *
 *       void AttachTo(const MyJavaClass::LocalRef& instance)
 *       {
 *           MyJavaClass::Natives<MyClass>::AttachNative(instance, this);
 *
 *           // "instance" owns "this" through the RefPtr, so the C++ object
 *           // may be destroyed as soon as instance.disposeNative() is called.
 *       }
 *   };
 *
 * * In other cases, the Java instance will store and own a pointer to the C++
 *   object itself. This pointer must not be stored or deleted elsewhere. To
 *   attach a Java instance to a C++ instance, pass in a reference to a
 *   UniquePtr of the C++ class (i.e. UniquePtr<MyClass>).
 *
 *   class MyClass : public MyJavaClass::Natives<MyClass>
 *   {
 *       // ...
 *
 *   public:
 *       using MyJavaClass::Natives<MyClass>::DisposeNative;
 *
 *       static void AttachTo(const MyJavaClass::LocalRef& instance)
 *       {
 *           MyJavaClass::Natives<MyClass>::AttachNative(
 *                   instance, mozilla::MakeUnique<MyClass>());
 *
 *           // "instance" owns the newly created C++ object, so the C++
 *           // object is destroyed as soon as instance.disposeNative() is
 *           // called.
 *       }
 *   };
 */

namespace detail {

/**
 * Type trait that determines whether a given class has a member named
 * T::OnWeakNonIntrusiveDetach.
 *
 * Example usage:
 * class Foo {};
 * class Bar {
 *  public:
 *   void OnWeakNonIntrusiveDetach(already_AddRefed<nsIRunnable> aRunnable);
 * };
 *
 * constexpr bool foo = HasWeakNonIntrusiveDetach<Foo>::value; // Expect false
 * constexpr bool bar = HasWeakNonIntrusiveDetach<Bar>::value; // Expect true
 */
template <typename, typename = std::void_t<>>
struct HasWeakNonIntrusiveDetach : std::false_type {};

template <typename T>
struct HasWeakNonIntrusiveDetach<
    T, std::void_t<decltype(std::declval<T>().OnWeakNonIntrusiveDetach(
           std::declval<already_AddRefed<nsIRunnable>>()))>> : std::true_type {
};

/**
 * Type trait that determines whether a given class is refcounted, ie. it has
 * both T::AddRef and T::Release methods.
 *
 * Example usage:
 * class Foo {};
 * class Bar {
 *  public:
 *   void AddRef();
 *   void Release();
 * };
 *
 * constexpr bool foo = IsRefCounted<Foo>::value; // Expect false
 * constexpr bool bar = IsRefCounted<Bar>::value; // Expect true
 */
template <typename, typename = std::void_t<>>
struct IsRefCounted : std::false_type {};

template <typename T>
struct IsRefCounted<T, std::void_t<decltype(std::declval<T>().AddRef(),
                                            std::declval<T>().Release())>>
    : std::true_type {};

/**
 * This enum is used for classifying the type of pointer that is stored
 * within a NativeWeakPtr. This classification is different from the one used
 * for normal native pointers.
 */
enum class NativePtrInternalType : size_t {
  OWNING = 1,
  WEAK = 2,
  REFPTR = 3,
};

/**
 * NativePtrInternalPicker uses some C++ SFINAE template-fu to figure out
 * what type of pointer the class specified by Impl needs to be.
 *
 * It does this by supplying multiple overloads of a method named Test.
 * Various overloads are enabled or disabled depending on whether or not Impl
 * can possibly support them.
 *
 * Each overload "returns" a reference to an array whose size corresponds to the
 * value of each enum in NativePtrInternalType. That size is then converted back
 * to the enum value, yielding the right type.
 */
template <class Impl>
class NativePtrInternalPicker {
  // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK
  template <class I>
  static std::enable_if_t<
      std::is_base_of<SupportsWeakPtr, I>::value,
      char (&)[static_cast<size_t>(NativePtrInternalType::WEAK)]>
  Test(char);

  // Enable if Impl implements AddRef and Release, yielding type REFPTR
  template <class I, typename = decltype(&I::AddRef, &I::Release)>
  static char (&Test(int))[static_cast<size_t>(NativePtrInternalType::REFPTR)];

  // This overload uses '...' as its param to make its arguments less specific;
  // the compiler prefers more-specific overloads to less-specific ones.
  // OWNING is the fallback type.
  template <class>
  static char (&Test(...))[static_cast<size_t>(NativePtrInternalType::OWNING)];

 public:
  // Given a hypothetical function call Test<Impl>, convert the size of its
  // resulting array back into a NativePtrInternalType enum value.
  static const NativePtrInternalType value = static_cast<NativePtrInternalType>(
      sizeof(Test<Impl>('\0')) / sizeof(char));
};

/**
 * This enum is used for classifying the type of pointer that is stored in a
 * JNIObject's handle.
 *
 * We have two different weak pointer types:
 *   * WEAK_INTRUSIVE is a pointer to a class that derives from
 *     mozilla::SupportsWeakPtr.
 *   * WEAK_NON_INTRUSIVE is a pointer to a class that does not have any
 *     internal support for weak pointers, but does supply a
 *     OnWeakNonIntrusiveDetach method.
 */
enum class NativePtrType : size_t {
  OWNING = 1,
  WEAK_INTRUSIVE = 2,
  WEAK_NON_INTRUSIVE = 3,
  REFPTR = 4,
};

/**
 * NativePtrPicker uses some C++ SFINAE template-fu to figure out what type of
 * pointer the class specified by Impl needs to be.
 *
 * It does this by supplying multiple overloads of a method named Test.
 * Various overloads are enabled or disabled depending on whether or not Impl
 * can possibly support them.
 *
 * Each overload "returns" a reference to an array whose size corresponds to the
 * value of each enum in NativePtrInternalType. That size is then converted back
 * to the enum value, yielding the right type.
 */
template <class Impl>
class NativePtrPicker {
  // Just shorthand for each overload's return type
  template <NativePtrType PtrType>
  using ResultTypeT = char (&)[static_cast<size_t>(PtrType)];

  // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE
  template <typename I>
  static auto Test(void*)
      -> std::enable_if_t<std::is_base_of<SupportsWeakPtr, I>::value,
                          ResultTypeT<NativePtrType::WEAK_INTRUSIVE>>;

  // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type
  // WEAK_NON_INTRUSIVE
  template <typename I>
  static auto Test(void*)
      -> std::enable_if_t<HasWeakNonIntrusiveDetach<I>::value,
                          ResultTypeT<NativePtrType::WEAK_NON_INTRUSIVE>>;

  // We want the WEAK_NON_INTRUSIVE overload to take precedence over this one,
  // so we only enable this overload if Impl is refcounted AND it does not
  // implement OnWeakNonIntrusiveDetach. Yields type REFPTR.
  template <typename I>
  static auto Test(void*) -> std::enable_if_t<
      std::conjunction_v<IsRefCounted<I>,
                         std::negation<HasWeakNonIntrusiveDetach<I>>>,
      ResultTypeT<NativePtrType::REFPTR>>;

  // This overload uses '...' as its param to make its arguments less specific;
  // the compiler prefers more-specific overloads to less-specific ones.
  // OWNING is the fallback type.
  template <typename>
  static char (&Test(...))[static_cast<size_t>(NativePtrType::OWNING)];

 public:
  // Given a hypothetical function call Test<Impl>, convert the size of its
  // resulting array back into a NativePtrType enum value.
  static const NativePtrType value =
      static_cast<NativePtrType>(sizeof(Test<Impl>(nullptr)));
};

template <class Impl>
inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) {
  if (!handle) {
    if (!env->ExceptionCheck()) {
      ThrowException(env, "java/lang/NullPointerException",
                     NullHandle<Impl>().str);
    }
    return 0;
  }
  return handle;
}

/**
 * This struct is used to describe various traits of a native pointer of type
 * Impl that will be attached to a JNIObject.
 *
 * See the definition of the NativePtrType::OWNING specialization for comments
 * describing the required fields.
 */
template <class Impl, NativePtrType Type = NativePtrPicker<Impl>::value>
struct NativePtrTraits;

template <class Impl>
struct NativePtrTraits<Impl, /* Type = */ NativePtrType::OWNING> {
  using AccessorType =
      Impl*;  // Pointer-like type returned by Access() (an actual pointer in
              // this case, but this is not strictly necessary)
  using HandleType = Impl*;  // Type of the pointer stored in JNIObject.mHandle
  using RefType = Impl*;     // Type of the pointer returned by Get()

  /**
   * Returns a RefType to the native implementation belonging to
   * the given Java object.
   */
  static RefType Get(JNIEnv* env, jobject instance) {
    static_assert(
        std::is_same<HandleType, RefType>::value,
        "HandleType and RefType must be identical for owning pointers");
    return reinterpret_cast<HandleType>(
        CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
  }

  /**
   * Returns a RefType to the native implementation belonging to
   * the given Java object.
   */
  template <class LocalRef>
  static RefType Get(const LocalRef& instance) {
    return Get(instance.Env(), instance.Get());
  }

  /**
   * Given a RefType, returns the pointer-like AccessorType used for
   * manipulating the native object.
   */
  static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) {
    static_assert(
        std::is_same<AccessorType, RefType>::value,
        "AccessorType and RefType must be identical for owning pointers");
    return aImpl;
  }

  /**
   * Set the JNIObject's handle to the provided pointer, clearing any previous
   * handle if necessary.
   */
  template <class LocalRef>
  static void Set(const LocalRef& instance, UniquePtr<Impl>&& ptr) {
    Clear(instance);
    SetNativeHandle(instance.Env(), instance.Get(),
                    reinterpret_cast<uintptr_t>(ptr.release()));
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());
  }

  /**
   * Clear the JNIObject's handle.
   */
  template <class LocalRef>
  static void Clear(const LocalRef& instance) {
    UniquePtr<Impl> ptr(reinterpret_cast<RefType>(
        GetNativeHandle(instance.Env(), instance.Get())));
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());

    if (ptr) {
      SetNativeHandle(instance.Env(), instance.Get(), 0);
      MOZ_CATCH_JNI_EXCEPTION(instance.Env());
    }
  }
};

template <class Impl>
struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_INTRUSIVE> {
  using AccessorType = Impl*;
  using HandleType = WeakPtr<Impl>*;
  using RefType = WeakPtr<Impl>;

  static RefType Get(JNIEnv* env, jobject instance) {
    const auto ptr = reinterpret_cast<HandleType>(
        CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
    return *ptr;
  }

  template <class LocalRef>
  static RefType Get(const LocalRef& instance) {
    return Get(instance.Env(), instance.Get());
  }

  static AccessorType Access(RefType aPtr, JNIEnv* aEnv = nullptr) {
    AccessorType const impl = *aPtr;
    if (!impl) {
      JNIEnv* env = aEnv ? aEnv : mozilla::jni::GetEnvForThread();
      ThrowException(env, "java/lang/NullPointerException",
                     NullWeakPtr<Impl>().str);
    }

    return impl;
  }

  template <class LocalRef>
  static void Set(const LocalRef& instance, Impl* ptr) {
    // Create the new handle first before clearing any old handle, so the
    // new handle is guaranteed to have different value than any old handle.
    const uintptr_t handle =
        reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr));
    Clear(instance);
    SetNativeHandle(instance.Env(), instance.Get(), handle);
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());
  }

  template <class LocalRef>
  static void Clear(const LocalRef& instance) {
    const auto ptr = reinterpret_cast<HandleType>(
        GetNativeHandle(instance.Env(), instance.Get()));
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());

    if (ptr) {
      SetNativeHandle(instance.Env(), instance.Get(), 0);
      MOZ_CATCH_JNI_EXCEPTION(instance.Env());
      delete ptr;
    }
  }
};

template <class Impl>
struct NativePtrTraits<Impl, /* Type = */ NativePtrType::REFPTR> {
  using AccessorType = Impl*;
  using HandleType = RefPtr<Impl>*;
  using RefType = Impl*;

  static RefType Get(JNIEnv* env, jobject instance) {
    const auto ptr = reinterpret_cast<HandleType>(
        CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
    if (!ptr) {
      return nullptr;
    }

    MOZ_ASSERT(*ptr);
    return *ptr;
  }

  template <class LocalRef>
  static RefType Get(const LocalRef& instance) {
    return Get(instance.Env(), instance.Get());
  }

  static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) {
    static_assert(std::is_same<AccessorType, RefType>::value,
                  "AccessorType and RefType must be identical for refpointers");
    return aImpl;
  }

  template <class LocalRef>
  static void Set(const LocalRef& instance, RefType ptr) {
    // Create the new handle first before clearing any old handle, so the
    // new handle is guaranteed to have different value than any old handle.
    const uintptr_t handle = reinterpret_cast<uintptr_t>(new RefPtr<Impl>(ptr));
    Clear(instance);
    SetNativeHandle(instance.Env(), instance.Get(), handle);
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());
  }

  template <class LocalRef>
  static void Clear(const LocalRef& instance) {
    const auto ptr = reinterpret_cast<HandleType>(
        GetNativeHandle(instance.Env(), instance.Get()));
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());

    if (ptr) {
      SetNativeHandle(instance.Env(), instance.Get(), 0);
      MOZ_CATCH_JNI_EXCEPTION(instance.Env());
      delete ptr;
    }
  }
};

}  // namespace detail

// Forward declarations
template <typename NativeImpl>
class NativeWeakPtr;
template <typename NativeImpl>
class NativeWeakPtrHolder;

namespace detail {

/**
 * Given the class of a native implementation, as well as its
 * NativePtrInternalType, resolve traits for that type that will be used by
 * the NativeWeakPtrControlBlock.
 *
 * Note that we only implement specializations for OWNING and REFPTR types,
 * as a WEAK_INTRUSIVE type should not be using NativeWeakPtr anyway. The build
 * will fail if such an attempt is made.
 *
 * Traits need to implement two things:
 * 1. A |Type| field that resolves to a pointer type to be stored in the
 *    JNIObject's handle. It is assumed that setting a |Type| object to nullptr
 *    is sufficient to delete the underlying object.
 * 2. A static |AsRaw| method that converts a pointer of |Type| into a raw
 *    pointer.
 */
template <
    typename NativeImpl,
    NativePtrInternalType PtrType =
        ::mozilla::jni::detail::NativePtrInternalPicker<NativeImpl>::value>
struct NativeWeakPtrControlBlockStorageTraits;

template <typename NativeImpl>
struct NativeWeakPtrControlBlockStorageTraits<
    NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> {
  using Type = UniquePtr<NativeImpl>;

  static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); }
};

template <typename NativeImpl>
struct NativeWeakPtrControlBlockStorageTraits<
    NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> {
  using Type = RefPtr<NativeImpl>;

  static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); }
};

// Forward Declaration
template <typename NativeImpl>
class Accessor;

/**
 * This class contains the shared data that is referenced by all NativeWeakPtr
 * objects that reference the same object.
 *
 * It retains a WeakRef to the Java object that owns this native object.
 * It uses a RWLock to control access to the native pointer itself.
 * Read locks are used when accessing the pointer (even when calling non-const
 * methods on the native object).
 * A write lock is only used when it is time to destroy the native object and
 * we need to clear the value of mNativeImpl.
 */
template <typename NativeImpl>
class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final {
 public:
  using StorageTraits = NativeWeakPtrControlBlockStorageTraits<NativeImpl>;
  using StorageType = typename StorageTraits::Type;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeWeakPtrControlBlock)

  NativeWeakPtrControlBlock(const NativeWeakPtrControlBlock&) = delete;
  NativeWeakPtrControlBlock(NativeWeakPtrControlBlock&&) = delete;
  NativeWeakPtrControlBlock& operator=(const NativeWeakPtrControlBlock&) =
      delete;
  NativeWeakPtrControlBlock& operator=(NativeWeakPtrControlBlock&&) = delete;

  // This is safe to call on any thread because mJavaOwner is immutable.
  mozilla::jni::Object::WeakRef GetJavaOwner() const { return mJavaOwner; }

 private:
  NativeWeakPtrControlBlock(::mozilla::jni::Object::Param aJavaOwner,
                            StorageType&& aNativeImpl)
      : mJavaOwner(aJavaOwner),
        mLock("mozilla::jni::detail::NativeWeakPtrControlBlock"),
        mNativeImpl(std::move(aNativeImpl)) {}

  ~NativeWeakPtrControlBlock() {
    // Make sure that somebody, somewhere, has detached us before destroying.
    MOZ_ASSERT(!(*this));
  }

  /**
   * Clear the native pointer so that subsequent accesses to the native pointer
   * via this control block are no longer available.
   *
   * We return the native pointer to the caller so that it may proceed with
   * cleaning up its resources.
   */
  StorageType Clear() {
    StorageType nativeImpl(nullptr);

    {  // Scope for lock
      AutoWriteLock lock(mLock);
      std::swap(mNativeImpl, nativeImpl);
    }

    return nativeImpl;
  }

  MOZ_PUSH_IGNORE_THREAD_SAFETY
  void Lock() const { mLock.ReadLock(); }

  void Unlock() const { mLock.ReadUnlock(); }
  MOZ_POP_THREAD_SAFETY

#if defined(DEBUG)
  // This is kind of expensive, so we only support it in debug builds.
  explicit operator bool() const {
    AutoReadLock lock(mLock);
    return !!mNativeImpl;
  }
#endif  // defined(DEBUG)

 private:
  friend class Accessor<NativeImpl>;
  friend class NativeWeakPtr<NativeImpl>;
  friend class NativeWeakPtrHolder<NativeImpl>;

 private:
  const mozilla::jni::Object::WeakRef mJavaOwner;
  mutable RWLock mLock MOZ_UNANNOTATED;  // Protects mNativeImpl
  StorageType mNativeImpl;
};

/**
 * If you want to temporarily access the object held by a NativeWeakPtr, you
 * must obtain one of these Accessor objects from the pointer. Access must
 * be done _exclusively_ using once of these objects!
 */
template <typename NativeImpl>
class MOZ_STACK_CLASS Accessor final {
 public:
  ~Accessor() {
    if (mCtlBlock) {
      mCtlBlock->Unlock();
    }
  }

  // Check whether the object is still valid before doing anything else
  explicit operator bool() const { return mCtlBlock && mCtlBlock->mNativeImpl; }

  // Normal member access
  NativeImpl* operator->() const {
    return NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw(
        mCtlBlock->mNativeImpl);
  }

  // This allows us to support calling a pointer to a member function
  template <typename Member>
  auto operator->*(Member aMember) const {
    NativeImpl* impl =
        NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw(
            mCtlBlock->mNativeImpl);
    return [impl, member = aMember](auto&&... aArgs) {
      return (impl->*member)(std::forward<decltype(aArgs)>(aArgs)...);
    };
  }

  // Only available for NativeImpl types that actually use refcounting.
  // The idea here is that it should be possible to obtain a strong ref from
  // a NativeWeakPtr if and only if NativeImpl supports refcounting.
  template <typename I = NativeImpl>
  auto AsRefPtr() const -> std::enable_if_t<IsRefCounted<I>::value, RefPtr<I>> {
    MOZ_ASSERT(I::HasThreadSafeRefCnt::value || NS_IsMainThread());
    return mCtlBlock->mNativeImpl;
  }

  Accessor(const Accessor&) = delete;
  Accessor(Accessor&&) = delete;
  Accessor& operator=(const Accessor&) = delete;
  Accessor& operator=(Accessor&&) = delete;

 private:
  explicit Accessor(
      const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock)
      : mCtlBlock(aCtlBlock) {
    if (aCtlBlock) {
      aCtlBlock->Lock();
    }
  }

 private:
  friend class NativeWeakPtr<NativeImpl>;
  friend class NativeWeakPtrHolder<NativeImpl>;

 private:
  const RefPtr<NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
};

}  // namespace detail

using DetachPromise = mozilla::MozPromise<bool, nsresult, true>;

/**
 * This class implements support for thread-safe weak pointers to native objects
 * that are owned by Java objects deriving from JNIObject.
 *
 * Any code that wants to access such a native object must have a copy of
 * a NativeWeakPtr to that object.
 */
template <typename NativeImpl>
class NativeWeakPtr {
 public:
  using Accessor = detail::Accessor<NativeImpl>;

  /**
   * Call this method to access the underlying object referenced by this
   * NativeWeakPtr.
   *
   * Always check the returned Accessor object for availability before calling
   * methods on it.
   *
   * For example, given:
   *
   * NativeWeakPtr<Foo> foo;
   * auto accessor = foo.Access();
   * if (accessor) {
   *   // Okay, safe to work with
   *   accessor->DoStuff();
   * } else {
   *   // The object's strong reference was cleared and is no longer available!
   * }
   */
  Accessor Access() const { return Accessor(mCtlBlock); }

  /**
   * Detach the underlying object's strong reference from its owning Java object
   * and clean it up.
   */
  RefPtr<DetachPromise> Detach();

  /**
   * This method does not indicate whether or not the weak pointer is still
   * valid; it only indicates whether we're actually attached to one.
   */
  bool IsAttached() const { return !!mCtlBlock; }

  /**
   * Does this pointer reference the same object as the one referenced by the
   * provided Accessor?
   */
  bool IsSame(const Accessor& aAccessor) const {
    return mCtlBlock == aAccessor.mCtlBlock;
  }

  /**
   * Does this pointer reference the same object as the one referenced by the
   * provided Control Block?
   */
  bool IsSame(const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>&
                  aOther) const {
    return mCtlBlock == aOther;
  }

  NativeWeakPtr() = default;
  MOZ_IMPLICIT NativeWeakPtr(decltype(nullptr)) {}
  NativeWeakPtr(const NativeWeakPtr& aOther) = default;
  NativeWeakPtr(NativeWeakPtr&& aOther) = default;
  NativeWeakPtr& operator=(const NativeWeakPtr& aOther) = default;
  NativeWeakPtr& operator=(NativeWeakPtr&& aOther) = default;

  NativeWeakPtr& operator=(decltype(nullptr)) {
    mCtlBlock = nullptr;
    return *this;
  }

 protected:
  // Construction of initial NativeWeakPtr for aCtlBlock
  explicit NativeWeakPtr(
      already_AddRefed<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock)
      : mCtlBlock(aCtlBlock) {}

 private:
  // Construction of subsequent NativeWeakPtrs for aCtlBlock
  explicit NativeWeakPtr(
      const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock)
      : mCtlBlock(aCtlBlock) {}

  friend class NativeWeakPtrHolder<NativeImpl>;

 protected:
  RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
};

/**
 * A pointer to an instance of this class should be stored in a Java object's
 * JNIObject handle. New instances of native objects wrapped by NativeWeakPtr
 * are created using the static methods of this class.
 *
 * Why do we have distinct methods here instead of using AttachNative like other
 * pointer types that may be stored in JNIObject?
 *
 * Essentially, we want the creation and use of NativeWeakPtr to be as
 * deliberate as possible. Forcing a different creation mechanism is part of
 * that emphasis.
 *
 * Example:
 *
 * class NativeFoo {
 *  public:
 *   NativeFoo();
 *   void Bar();
 *   // The following method is required to be used with NativeWeakPtr
 *   void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer);
 * };
 *
 * java::Object::LocalRef javaObj(...);
 *
 * // Create a new Foo that is attached to javaObj
 * auto weakFoo = NativeWeakPtrHolder<NativeFoo>::Attach(javaObj);
 *
 * // Now I can save weakFoo, access it, do whatever I want
 * if (auto accWeakFoo = weakFoo.Access()) {
 *   accWeakFoo->Bar();
 * }
 *
 * // Detach from javaObj and clean up
 * weakFoo.Detach();
 */
template <typename NativeImpl>
class MOZ_HEAP_CLASS NativeWeakPtrHolder final
    : public NativeWeakPtr<NativeImpl> {
  using Base = NativeWeakPtr<NativeImpl>;

 public:
  using Accessor = typename Base::Accessor;
  using StorageTraits =
      typename detail::NativeWeakPtrControlBlock<NativeImpl>::StorageTraits;
  using StorageType = typename StorageTraits::Type;

  /**
   * Create a new NativeImpl object, wrap it in a NativeWeakPtr, and store it
   * in the Java object's JNIObject handle.
   *
   * @return A NativeWeakPtr object that references the newly-attached object.
   */
  template <typename Cls, typename JNIType, typename... Args>
  static NativeWeakPtr<NativeImpl> Attach(const Ref<Cls, JNIType>& aJavaObject,
                                          Args&&... aArgs) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());

    StorageType nativeImpl(new NativeImpl(std::forward<Args>(aArgs)...));
    return AttachInternal(aJavaObject, std::move(nativeImpl));
  }

  /**
   * Given a new NativeImpl object, wrap it in a NativeWeakPtr, and store it
   * in the Java object's JNIObject handle.
   *
   * @return A NativeWeakPtr object that references the newly-attached object.
   */
  template <typename Cls, typename JNIType>
  static NativeWeakPtr<NativeImpl> AttachExisting(
      const Ref<Cls, JNIType>& aJavaObject,
      already_AddRefed<NativeImpl> aNativeImpl) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());

    StorageType nativeImpl(aNativeImpl);
    return AttachInternal(aJavaObject, std::move(nativeImpl));
  }

  ~NativeWeakPtrHolder() = default;

  MOZ_IMPLICIT NativeWeakPtrHolder(decltype(nullptr)) = delete;
  NativeWeakPtrHolder(const NativeWeakPtrHolder&) = delete;
  NativeWeakPtrHolder(NativeWeakPtrHolder&&) = delete;
  NativeWeakPtrHolder& operator=(const NativeWeakPtrHolder&) = delete;
  NativeWeakPtrHolder& operator=(NativeWeakPtrHolder&&) = delete;
  NativeWeakPtrHolder& operator=(decltype(nullptr)) = delete;

 private:
  template <typename Cls>
  NativeWeakPtrHolder(const LocalRef<Cls>& aJavaObject,
                      StorageType&& aNativeImpl)
      : NativeWeakPtr<NativeImpl>(
            do_AddRef(new NativeWeakPtrControlBlock<NativeImpl>(
                aJavaObject, std::move(aNativeImpl)))) {}

  /**
   * Internal function that actually wraps the native pointer, binds it to the
   * JNIObject, and then returns the NativeWeakPtr result.
   */
  template <typename Cls, typename JNIType>
  static NativeWeakPtr<NativeImpl> AttachInternal(
      const Ref<Cls, JNIType>& aJavaObject, StorageType&& aPtr) {
    auto localJavaObject = ToLocalRef(aJavaObject);
    NativeWeakPtrHolder<NativeImpl>* holder =
        new NativeWeakPtrHolder<NativeImpl>(localJavaObject, std::move(aPtr));
    static_assert(
        NativePtrPicker<NativeImpl>::value == NativePtrType::WEAK_NON_INTRUSIVE,
        "This type is not compatible with mozilla::jni::NativeWeakPtr");
    NativePtrTraits<NativeImpl>::Set(localJavaObject, holder);
    return NativeWeakPtr<NativeImpl>(holder->mCtlBlock);
  }
};

namespace detail {

/**
 * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type.
 */
template <class Impl>
struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_NON_INTRUSIVE> {
  using AccessorType = typename NativeWeakPtrHolder<Impl>::Accessor;
  using HandleType = NativeWeakPtrHolder<Impl>*;
  using RefType = NativeWeakPtrHolder<Impl>* const;

  static RefType Get(JNIEnv* env, jobject instance) {
    return GetHandle(env, instance);
  }

  template <typename Cls>
  static RefType Get(const LocalRef<Cls>& instance) {
    return GetHandle(instance.Env(), instance.Get());
  }

  static AccessorType Access(RefType aPtr) { return aPtr->Access(); }

  template <typename Cls>
  static void Set(const LocalRef<Cls>& instance, HandleType ptr) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());
    const uintptr_t handle = reinterpret_cast<uintptr_t>(ptr);
    Clear(instance);
    SetNativeHandle(instance.Env(), instance.Get(), handle);
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());
  }

  template <typename Cls>
  static void Clear(const LocalRef<Cls>& instance) {
    auto ptr = reinterpret_cast<HandleType>(
        GetNativeHandle(instance.Env(), instance.Get()));
    MOZ_CATCH_JNI_EXCEPTION(instance.Env());

    if (!ptr) {
      return;
    }

    ptr->Detach();
  }

  // This call is not safe to do unless we know for sure that instance's
  // native handle has not changed. It is up to NativeWeakPtrDetachRunnable
  // to perform this check.
  template <typename Cls>
  static void ClearFinish(const LocalRef<Cls>& instance) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());
    JNIEnv* const env = instance.Env();
    auto ptr =
        reinterpret_cast<HandleType>(GetNativeHandle(env, instance.Get()));
    MOZ_CATCH_JNI_EXCEPTION(env);
    MOZ_RELEASE_ASSERT(!!ptr);

    SetNativeHandle(env, instance.Get(), 0);
    MOZ_CATCH_JNI_EXCEPTION(env);
    // Deletion of ptr is done by the caller
  }

  // The call is stale if the native object has been destroyed on the
  // Gecko side, but the Java object is still attached to it through
  // a weak pointer. Stale calls should be discarded. Note that it's
  // an error if holder is nullptr here; we return false but the
  // native call will throw an error.
  template <class LocalRef>
  static bool IsStale(const LocalRef& instance) {
    JNIEnv* const env = mozilla::jni::GetEnvForThread();

    // We cannot use Get here because that method throws an exception when the
    // object is null, which is a valid state for a stale call.
    const auto holder =
        reinterpret_cast<HandleType>(GetNativeHandle(env, instance.Get()));
    MOZ_CATCH_JNI_EXCEPTION(env);

    if (!holder || !holder->IsAttached()) {
      return true;
    }

    auto acc(holder->Access());
    return !acc;
  }

 private:
  static HandleType GetHandle(JNIEnv* env, jobject instance) {
    return reinterpret_cast<HandleType>(
        CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance)));
  }

  template <typename Cls>
  static HandleType GetHandle(const LocalRef<Cls>& instance) {
    return GetHandle(instance.Env(), instance.Get());
  }

  friend class NativeWeakPtrHolder<Impl>;
};

}  // namespace detail

using namespace detail;

/**
 * For JNI native methods that are dispatched to a proxy, i.e. using
 * @WrapForJNI(dispatchTo = "proxy"), the implementing C++ class must provide a
 * OnNativeCall member. Subsequently, every native call is automatically
 * wrapped in a functor object, and the object is passed to OnNativeCall. The
 * OnNativeCall implementation can choose to invoke the call, save it, dispatch
 * it to a different thread, etc. Each copy of functor may only be invoked
 * once.
 *
 * class MyClass : public MyJavaClass::Natives<MyClass>
 * {
 *     // ...
 *
 *     template<class Functor>
 *     class ProxyRunnable final : public Runnable
 *     {
 *         Functor mCall;
 *     public:
 *         ProxyRunnable(Functor&& call) : mCall(std::move(call)) {}
 *         virtual void run() override { mCall(); }
 *     };
 *
 * public:
 *     template<class Functor>
 *     static void OnNativeCall(Functor&& call)
 *     {
 *         RunOnAnotherThread(new ProxyRunnable(std::move(call)));
 *     }
 * };
 */

namespace detail {

// ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied
// call may happen outside of the original JNI native call, we must save all
// JNI ref arguments as global refs to avoid the arguments going out of scope.
template <typename T>
struct ProxyArg {
  static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>,
                "T must be primitive type");

  // Primitive types can be saved by value.
  typedef T Type;
  typedef typename TypeAdapter<T>::JNIType JNIType;

  static void Clear(JNIEnv* env, Type&) {}

  static Type From(JNIEnv* env, JNIType val) {
    return TypeAdapter<T>::ToNative(env, val);
  }
};

template <class C, typename T>
struct ProxyArg<Ref<C, T>> {
  // Ref types need to be saved by global ref.
  typedef typename C::GlobalRef Type;
  typedef typename TypeAdapter<Ref<C, T>>::JNIType JNIType;

  static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); }

  static Type From(JNIEnv* env, JNIType val) {
    return Type(env, C::Ref::From(val));
  }
};

template <typename C>
struct ProxyArg<const C&> : ProxyArg<C> {};
template <>
struct ProxyArg<StringParam> : ProxyArg<String::Ref> {};
template <class C>
struct ProxyArg<LocalRef<C>> : ProxyArg<typename C::Ref> {};

// ProxyNativeCall implements the functor object that is passed to OnNativeCall
template <class Impl, class Owner, bool IsStatic,
          bool HasThisArg /* has instance/class local ref in the call */,
          typename... Args>
class ProxyNativeCall {
  // "this arg" refers to the Class::LocalRef (for static methods) or
  // Owner::LocalRef (for instance methods) that we optionally (as indicated
  // by HasThisArg) pass into the destination C++ function.
  using ThisArgClass = std::conditional_t<IsStatic, Class, Owner>;
  using ThisArgJNIType = std::conditional_t<IsStatic, jclass, jobject>;

  // Type signature of the destination C++ function, which matches the
  // Method template parameter in NativeStubImpl::Wrap.
  using NativeCallType = std::conditional_t<
      IsStatic,
      std::conditional_t<HasThisArg, void (*)(const Class::LocalRef&, Args...),
                         void (*)(Args...)>,
      std::conditional_t<
          HasThisArg, void (Impl::*)(const typename Owner::LocalRef&, Args...),
          void (Impl::*)(Args...)>>;

  // Destination C++ function.
  NativeCallType mNativeCall;
  // Saved this arg.
  typename ThisArgClass::GlobalRef mThisArg;
  // Saved arguments.
  std::tuple<typename ProxyArg<Args>::Type...> mArgs;

  // We cannot use IsStatic and HasThisArg directly (without going through
  // extra hoops) because GCC complains about invalid overloads, so we use
  // another pair of template parameters, Static and ThisArg.

  template <bool Static, bool ThisArg, size_t... Indices>
  std::enable_if_t<Static && ThisArg, void> Call(
      const Class::LocalRef& cls, std::index_sequence<Indices...>) const {
    (*mNativeCall)(cls, std::get<Indices>(mArgs)...);
  }

  template <bool Static, bool ThisArg, size_t... Indices>
  std::enable_if_t<Static && !ThisArg, void> Call(
      const Class::LocalRef& cls, std::index_sequence<Indices...>) const {
    (*mNativeCall)(std::get<Indices>(mArgs)...);
  }

  template <bool Static, bool ThisArg, size_t... Indices>
  std::enable_if_t<!Static && ThisArg, void> Call(
      const typename Owner::LocalRef& inst,
      std::index_sequence<Indices...>) const {
    auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst));
    MOZ_CATCH_JNI_EXCEPTION(inst.Env());
    (impl->*mNativeCall)(inst, std::get<Indices>(mArgs)...);
  }

  template <bool Static, bool ThisArg, size_t... Indices>
  std::enable_if_t<!Static && !ThisArg, void> Call(
      const typename Owner::LocalRef& inst,
      std::index_sequence<Indices...>) const {
    auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst));
    MOZ_CATCH_JNI_EXCEPTION(inst.Env());
    (impl->*mNativeCall)(std::get<Indices>(mArgs)...);
  }

  template <size_t... Indices>
  void Clear(JNIEnv* env, std::index_sequence<Indices...>) {
    int dummy[] = {
        (ProxyArg<Args>::Clear(env, std::get<Indices>(mArgs)), 0)...};
    mozilla::Unused << dummy;
  }

  static decltype(auto) GetNativeObject(Class::Param thisArg) {
    return nullptr;
  }

  static decltype(auto) GetNativeObject(typename Owner::Param thisArg) {
    return NativePtrTraits<Impl>::Access(
        NativePtrTraits<Impl>::Get(GetEnvForThread(), thisArg.Get()));
  }

 public:
  // The class that implements the call target.
  typedef Impl TargetClass;
  typedef typename ThisArgClass::Param ThisArgType;

  static const bool isStatic = IsStatic;

  ProxyNativeCall(ThisArgJNIType thisArg, NativeCallType nativeCall,
                  JNIEnv* env, typename ProxyArg<Args>::JNIType... args)
      : mNativeCall(nativeCall),
        mThisArg(env, ThisArgClass::Ref::From(thisArg)),
        mArgs(ProxyArg<Args>::From(env, args)...) {}

  ProxyNativeCall(ProxyNativeCall&&) = default;
  ProxyNativeCall(const ProxyNativeCall&) = default;

  // Get class ref for static calls or object ref for instance calls.
  typename ThisArgClass::Param GetThisArg() const { return mThisArg; }

  // Get the native object targeted by this call.
  // Returns nullptr for static calls.
  decltype(auto) GetNativeObject() const { return GetNativeObject(mThisArg); }

  // Return if target is the given function pointer / pointer-to-member.
  // Because we can only compare pointers of the same type, we use a
  // templated overload that is chosen only if given a different type of
  // pointer than our target pointer type.
  bool IsTarget(NativeCallType call) const { return call == mNativeCall; }
  template <typename T>
  bool IsTarget(T&&) const {
    return false;
  }

  // Redirect the call to another function / class member with the same
  // signature as the original target. Crash if given a wrong signature.
  void SetTarget(NativeCallType call) { mNativeCall = call; }
  template <typename T>
  void SetTarget(T&&) const {
    MOZ_CRASH();
  }

  void operator()() {
    JNIEnv* const env = GetEnvForThread();
    typename ThisArgClass::LocalRef thisArg(env, mThisArg);
    Call<IsStatic, HasThisArg>(thisArg, std::index_sequence_for<Args...>{});

    // Clear all saved global refs. We do this after the call is invoked,
    // and not inside the destructor because we already have a JNIEnv here,
    // so it's more efficient to clear out the saved args here. The
    // downside is that the call can only be invoked once.
    Clear(env, std::index_sequence_for<Args...>{});
    mThisArg.Clear(env);
  }
};

template <class Impl, bool HasThisArg, typename... Args>
struct Dispatcher {
  template <class Traits, bool IsStatic = Traits::isStatic,
            typename... ProxyArgs>
  static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::PROXY, void>
  Run(ProxyArgs&&... args) {
    Impl::OnNativeCall(
        ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
                        Args...>(std::forward<ProxyArgs>(args)...));
  }

  template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg,
            typename... ProxyArgs>
  static std::enable_if_t<
      Traits::dispatchTarget == DispatchTarget::GECKO_PRIORITY, void>
  Run(ThisArg thisArg, ProxyArgs&&... args) {
    // For a static method, do not forward the "this arg" (i.e. the class
    // local ref) if the implementation does not request it. This saves us
    // a pair of calls to add/delete global ref.
    auto proxy =
        ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
                        Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr,
                                 std::forward<ProxyArgs>(args)...);
    DispatchToGeckoPriorityQueue(
        NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy)));
  }

  template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg,
            typename... ProxyArgs>
  static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::GECKO, void>
  Run(ThisArg thisArg, ProxyArgs&&... args) {
    // For a static method, do not forward the "this arg" (i.e. the class
    // local ref) if the implementation does not request it. This saves us
    // a pair of calls to add/delete global ref.
    auto proxy =
        ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg,
                        Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr,
                                 std::forward<ProxyArgs>(args)...);
    NS_DispatchToMainThread(
        NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy)));
  }

  template <class Traits, bool IsStatic = false, typename... ProxyArgs>
  static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::CURRENT,
                          void>
  Run(ProxyArgs&&... args) {
    MOZ_CRASH("Unreachable code");
  }
};

}  // namespace detail

// Wrapper methods that convert arguments from the JNI types to the native
// types, e.g. from jobject to jni::Object::Ref. For instance methods, the
// wrapper methods also convert calls to calls on objects.
//
// We need specialization for static/non-static because the two have different
// signatures (jobject vs jclass and Impl::*Method vs *Method).
// We need specialization for return type, because void return type requires
// us to not deal with the return value.

// Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry
#ifdef __i386__
#  define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer))
#else
#  define MOZ_JNICALL JNICALL
#endif

template <class Traits, class Impl, class Args = typename Traits::Args>
class NativeStub;

template <class Traits, class Impl, typename... Args>
class NativeStub<Traits, Impl, jni::Args<Args...>> {
  using Owner = typename Traits::Owner;
  using ReturnType = typename Traits::ReturnType;

  static constexpr bool isStatic = Traits::isStatic;
  static constexpr bool isVoid = std::is_void_v<ReturnType>;

  struct VoidType {
    using JNIType = void;
  };
  using ReturnJNIType =
      typename std::conditional_t<isVoid, VoidType,
                                  TypeAdapter<ReturnType>>::JNIType;

  using ReturnTypeForNonVoidInstance =
      std::conditional_t<!isStatic && !isVoid, ReturnType, VoidType>;
  using ReturnTypeForVoidInstance =
      std::conditional_t<!isStatic && isVoid, ReturnType, VoidType&>;
  using ReturnTypeForNonVoidStatic =
      std::conditional_t<isStatic && !isVoid, ReturnType, VoidType>;
  using ReturnTypeForVoidStatic =
      std::conditional_t<isStatic && isVoid, ReturnType, VoidType&>;

  static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid,
                "Dispatched calls must have void return type");

 public:
  // Non-void instance method
  template <ReturnTypeForNonVoidInstance (Impl::*Method)(Args...)>
  static MOZ_JNICALL ReturnJNIType
  Wrap(JNIEnv* env, jobject instance,
       typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    auto impl = NativePtrTraits<Impl>::Access(
        NativePtrTraits<Impl>::Get(env, instance));
    if (!impl) {
      // There is a pending JNI exception at this point.
      return ReturnJNIType();
    }
    return TypeAdapter<ReturnType>::FromNative(
        env, (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...));
  }

  // Non-void instance method with instance reference
  template <ReturnTypeForNonVoidInstance (Impl::*Method)(
      const typename Owner::LocalRef&, Args...)>
  static MOZ_JNICALL ReturnJNIType
  Wrap(JNIEnv* env, jobject instance,
       typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    auto impl = NativePtrTraits<Impl>::Access(
        NativePtrTraits<Impl>::Get(env, instance));
    if (!impl) {
      // There is a pending JNI exception at this point.
      return ReturnJNIType();
    }
    auto self = Owner::LocalRef::Adopt(env, instance);
    const auto res = TypeAdapter<ReturnType>::FromNative(
        env, (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...));
    self.Forget();
    return res;
  }

  // Void instance method
  template <ReturnTypeForVoidInstance (Impl::*Method)(Args...)>
  static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance,
                               typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
      Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>(
          instance, Method, env, args...);
      return;
    }

    auto impl = NativePtrTraits<Impl>::Access(
        NativePtrTraits<Impl>::Get(env, instance));
    if (!impl) {
      // There is a pending JNI exception at this point.
      return;
    }
    (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...);
  }

  // Void instance method with instance reference
  template <ReturnTypeForVoidInstance (Impl::*Method)(
      const typename Owner::LocalRef&, Args...)>
  static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance,
                               typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
      Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>(
          instance, Method, env, args...);
      return;
    }

    auto impl = NativePtrTraits<Impl>::Access(
        NativePtrTraits<Impl>::Get(env, instance));
    if (!impl) {
      // There is a pending JNI exception at this point.
      return;
    }
    auto self = Owner::LocalRef::Adopt(env, instance);
    (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...);
    self.Forget();
  }

  // Overload for DisposeNative
  template <ReturnTypeForVoidInstance (*DisposeNative)(
      const typename Owner::LocalRef&)>
  static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
      using LocalRef = typename Owner::LocalRef;
      Dispatcher<Impl, /* HasThisArg */ false, const LocalRef&>::template Run<
          Traits, /* IsStatic */ true>(
          /* ThisArg */ nullptr, DisposeNative, env, instance);
      return;
    }

    auto self = Owner::LocalRef::Adopt(env, instance);
    DisposeNative(self);
    self.Forget();
  }

  // Non-void static method
  template <ReturnTypeForNonVoidStatic (*Method)(Args...)>
  static MOZ_JNICALL ReturnJNIType
  Wrap(JNIEnv* env, jclass, typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    return TypeAdapter<ReturnType>::FromNative(
        env, (*Method)(TypeAdapter<Args>::ToNative(env, args)...));
  }

  // Non-void static method with class reference
  template <ReturnTypeForNonVoidStatic (*Method)(const Class::LocalRef&,
                                                 Args...)>
  static MOZ_JNICALL ReturnJNIType
  Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    auto clazz = Class::LocalRef::Adopt(env, cls);
    const auto res = TypeAdapter<ReturnType>::FromNative(
        env, (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...));
    clazz.Forget();
    return res;
  }

  // Void static method
  template <ReturnTypeForVoidStatic (*Method)(Args...)>
  static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls,
                               typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
      Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>(
          cls, Method, env, args...);
      return;
    }

    (*Method)(TypeAdapter<Args>::ToNative(env, args)...);
  }

  // Void static method with class reference
  template <ReturnTypeForVoidStatic (*Method)(const Class::LocalRef&, Args...)>
  static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls,
                               typename TypeAdapter<Args>::JNIType... args) {
    MOZ_ASSERT_JNI_THREAD(Traits::callingThread);

    if (Traits::dispatchTarget != DispatchTarget::CURRENT) {
      Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>(
          cls, Method, env, args...);
      return;
    }

    auto clazz = Class::LocalRef::Adopt(env, cls);
    (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...);
    clazz.Forget();
  }
};

// Generate a JNINativeMethod from a native
// method's traits class and a wrapped stub.
template <class Traits, typename Ret, typename... Args>
constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*,
                                                                   Args...)) {
  return {Traits::name, Traits::signature, reinterpret_cast<void*>(stub)};
}

// Class inherited by implementing class.
template <class Cls, class Impl>
class NativeImpl {
  typedef typename Cls::template Natives<Impl> Natives;

  static bool sInited;

 public:
  static void Init() {
    if (sInited) {
      return;
    }
    const auto& ctx = typename Cls::Context();
    ctx.Env()->RegisterNatives(
        ctx.ClassRef(), Natives::methods,
        sizeof(Natives::methods) / sizeof(Natives::methods[0]));
    MOZ_CATCH_JNI_EXCEPTION(ctx.Env());
    sInited = true;
  }

 protected:
  // Associate a C++ instance with a Java instance.
  static void AttachNative(const typename Cls::LocalRef& instance,
                           SupportsWeakPtr* ptr) {
    static_assert(NativePtrPicker<Impl>::value == NativePtrType::WEAK_INTRUSIVE,
                  "Use another AttachNative for non-WeakPtr usage");
    return NativePtrTraits<Impl>::Set(instance, static_cast<Impl*>(ptr));
  }

  static void AttachNative(const typename Cls::LocalRef& instance,
                           UniquePtr<Impl>&& ptr) {
    static_assert(NativePtrPicker<Impl>::value == NativePtrType::OWNING,
                  "Use another AttachNative for WeakPtr or RefPtr usage");
    return NativePtrTraits<Impl>::Set(instance, std::move(ptr));
  }

  static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) {
    static_assert(NativePtrPicker<Impl>::value == NativePtrType::REFPTR,
                  "Use another AttachNative for non-RefPtr usage");
    return NativePtrTraits<Impl>::Set(instance, ptr);
  }

  // Get the C++ instance associated with a Java instance.
  // There is always a pending exception if the return value is nullptr.
  static decltype(auto) GetNative(const typename Cls::LocalRef& instance) {
    return NativePtrTraits<Impl>::Get(instance);
  }

  static void DisposeNative(const typename Cls::LocalRef& instance) {
    NativePtrTraits<Impl>::Clear(instance);
  }

  NativeImpl() {
    // Initialize on creation if not already initialized.
    Init();
  }
};

// Define static member.
template <class C, class I>
bool NativeImpl<C, I>::sInited;

}  // namespace jni
}  // namespace mozilla

#endif  // mozilla_jni_Natives_h__
