From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- widget/android/jni/Natives.h | 1540 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1540 insertions(+) create mode 100644 widget/android/jni/Natives.h (limited to 'widget/android/jni/Natives.h') diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h new file mode 100644 index 0000000000..45b351ba21 --- /dev/null +++ b/widget/android/jni/Natives.h @@ -0,0 +1,1540 @@ +/* -*- 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 +#include +#include +#include + +#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 +static NativeException NullHandle() { + return {FUNCTION_SIGNATURE}; +} + +template +static NativeException NullWeakPtr() { + return {FUNCTION_SIGNATURE}; +} + +namespace mozilla { + +template +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 + * { + * // ... + * + * public: + * using MyJavaClass::Natives::DisposeNative; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives::AttachNative( + * instance, static_cast(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 + * , public MyJavaClass::Natives + * { + * // ... + * + * public: + * using MyJavaClass::Natives::DisposeNative; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives::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). + * + * class MyClass : public MyJavaClass::Natives + * { + * // ... + * + * public: + * using MyJavaClass::Natives::DisposeNative; + * + * static void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives::AttachNative( + * instance, mozilla::MakeUnique()); + * + * // "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 aRunnable); + * }; + * + * constexpr bool foo = HasWeakNonIntrusiveDetach::value; // Expect false + * constexpr bool bar = HasWeakNonIntrusiveDetach::value; // Expect true + */ +template > +struct HasWeakNonIntrusiveDetach : std::false_type {}; + +template +struct HasWeakNonIntrusiveDetach< + T, std::void_t().OnWeakNonIntrusiveDetach( + std::declval>()))>> : 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::value; // Expect false + * constexpr bool bar = IsRefCounted::value; // Expect true + */ +template > +struct IsRefCounted : std::false_type {}; + +template +struct IsRefCounted().AddRef(), + std::declval().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 NativePtrInternalPicker { + // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK + template + static std::enable_if_t< + std::is_base_of::value, + char (&)[static_cast(NativePtrInternalType::WEAK)]> + Test(char); + + // Enable if Impl implements AddRef and Release, yielding type REFPTR + template + static char (&Test(int))[static_cast(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 + static char (&Test(...))[static_cast(NativePtrInternalType::OWNING)]; + + public: + // Given a hypothetical function call Test, convert the size of its + // resulting array back into a NativePtrInternalType enum value. + static const NativePtrInternalType value = static_cast( + sizeof(Test('\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 NativePtrPicker { + // Just shorthand for each overload's return type + template + using ResultTypeT = char (&)[static_cast(PtrType)]; + + // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE + template + static auto Test(void*) + -> std::enable_if_t::value, + ResultTypeT>; + + // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type + // WEAK_NON_INTRUSIVE + template + static auto Test(void*) + -> std::enable_if_t::value, + ResultTypeT>; + + // 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 + static auto Test(void*) -> std::enable_if_t< + std::conjunction_v, + std::negation>>, + ResultTypeT>; + + // 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 + static char (&Test(...))[static_cast(NativePtrType::OWNING)]; + + public: + // Given a hypothetical function call Test, convert the size of its + // resulting array back into a NativePtrType enum value. + static const NativePtrType value = + static_cast(sizeof(Test(nullptr))); +}; + +template +inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) { + if (!handle) { + if (!env->ExceptionCheck()) { + ThrowException(env, "java/lang/NullPointerException", + NullHandle().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 ::value> +struct NativePtrTraits; + +template +struct NativePtrTraits { + 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::value, + "HandleType and RefType must be identical for owning pointers"); + return reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + } + + /** + * Returns a RefType to the native implementation belonging to + * the given Java object. + */ + template + 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::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 + static void Set(const LocalRef& instance, UniquePtr&& ptr) { + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), + reinterpret_cast(ptr.release())); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + /** + * Clear the JNIObject's handle. + */ + template + static void Clear(const LocalRef& instance) { + UniquePtr ptr(reinterpret_cast( + 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 +struct NativePtrTraits { + using AccessorType = Impl*; + using HandleType = WeakPtr*; + using RefType = WeakPtr; + + static RefType Get(JNIEnv* env, jobject instance) { + const auto ptr = reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + return *ptr; + } + + template + 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().str); + } + + return impl; + } + + template + 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(new WeakPtr(ptr)); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template + static void Clear(const LocalRef& instance) { + const auto ptr = reinterpret_cast( + 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 +struct NativePtrTraits { + using AccessorType = Impl*; + using HandleType = RefPtr*; + using RefType = Impl*; + + static RefType Get(JNIEnv* env, jobject instance) { + const auto ptr = reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + if (!ptr) { + return nullptr; + } + + MOZ_ASSERT(*ptr); + return *ptr; + } + + template + 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::value, + "AccessorType and RefType must be identical for refpointers"); + return aImpl; + } + + template + 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(new RefPtr(ptr)); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template + static void Clear(const LocalRef& instance) { + const auto ptr = reinterpret_cast( + 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 +class NativeWeakPtr; +template +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::value> +struct NativeWeakPtrControlBlockStorageTraits; + +template +struct NativeWeakPtrControlBlockStorageTraits< + NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> { + using Type = UniquePtr; + + static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } +}; + +template +struct NativeWeakPtrControlBlockStorageTraits< + NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> { + using Type = RefPtr; + + static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } +}; + +// Forward Declaration +template +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 +class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final { + public: + using StorageTraits = NativeWeakPtrControlBlockStorageTraits; + 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; + friend class NativeWeakPtr; + friend class NativeWeakPtrHolder; + + 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 +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::AsRaw( + mCtlBlock->mNativeImpl); + } + + // This allows us to support calling a pointer to a member function + template + auto operator->*(Member aMember) const { + NativeImpl* impl = + NativeWeakPtrControlBlockStorageTraits::AsRaw( + mCtlBlock->mNativeImpl); + return [impl, member = aMember](auto&&... aArgs) { + return (impl->*member)(std::forward(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 + auto AsRefPtr() const -> std::enable_if_t::value, RefPtr> { + 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>& aCtlBlock) + : mCtlBlock(aCtlBlock) { + if (aCtlBlock) { + aCtlBlock->Lock(); + } + } + + private: + friend class NativeWeakPtr; + friend class NativeWeakPtrHolder; + + private: + const RefPtr> mCtlBlock; +}; + +} // namespace detail + +using DetachPromise = mozilla::MozPromise; + +/** + * 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 +class NativeWeakPtr { + public: + using Accessor = detail::Accessor; + + /** + * 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; + * 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 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>& + 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> aCtlBlock) + : mCtlBlock(aCtlBlock) {} + + private: + // Construction of subsequent NativeWeakPtrs for aCtlBlock + explicit NativeWeakPtr( + const RefPtr>& aCtlBlock) + : mCtlBlock(aCtlBlock) {} + + friend class NativeWeakPtrHolder; + + protected: + RefPtr> 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 aDisposer); + * }; + * + * java::Object::LocalRef javaObj(...); + * + * // Create a new Foo that is attached to javaObj + * auto weakFoo = NativeWeakPtrHolder::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 +class MOZ_HEAP_CLASS NativeWeakPtrHolder final + : public NativeWeakPtr { + using Base = NativeWeakPtr; + + public: + using Accessor = typename Base::Accessor; + using StorageTraits = + typename detail::NativeWeakPtrControlBlock::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 + static NativeWeakPtr Attach(const Ref& aJavaObject, + Args&&... aArgs) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + StorageType nativeImpl(new NativeImpl(std::forward(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 + static NativeWeakPtr AttachExisting( + const Ref& aJavaObject, + already_AddRefed 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 + NativeWeakPtrHolder(const LocalRef& aJavaObject, + StorageType&& aNativeImpl) + : NativeWeakPtr( + do_AddRef(new NativeWeakPtrControlBlock( + aJavaObject, std::move(aNativeImpl)))) {} + + /** + * Internal function that actually wraps the native pointer, binds it to the + * JNIObject, and then returns the NativeWeakPtr result. + */ + template + static NativeWeakPtr AttachInternal( + const Ref& aJavaObject, StorageType&& aPtr) { + auto localJavaObject = ToLocalRef(aJavaObject); + NativeWeakPtrHolder* holder = + new NativeWeakPtrHolder(localJavaObject, std::move(aPtr)); + static_assert( + NativePtrPicker::value == NativePtrType::WEAK_NON_INTRUSIVE, + "This type is not compatible with mozilla::jni::NativeWeakPtr"); + NativePtrTraits::Set(localJavaObject, holder); + return NativeWeakPtr(holder->mCtlBlock); + } +}; + +namespace detail { + +/** + * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type. + */ +template +struct NativePtrTraits { + using AccessorType = typename NativeWeakPtrHolder::Accessor; + using HandleType = NativeWeakPtrHolder*; + using RefType = NativeWeakPtrHolder* const; + + static RefType Get(JNIEnv* env, jobject instance) { + return GetHandle(env, instance); + } + + template + static RefType Get(const LocalRef& instance) { + return GetHandle(instance.Env(), instance.Get()); + } + + static AccessorType Access(RefType aPtr) { return aPtr->Access(); } + + template + static void Set(const LocalRef& instance, HandleType ptr) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + const uintptr_t handle = reinterpret_cast(ptr); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template + static void Clear(const LocalRef& instance) { + auto ptr = reinterpret_cast( + 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 + static void ClearFinish(const LocalRef& instance) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + JNIEnv* const env = instance.Env(); + auto ptr = + reinterpret_cast(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 + 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(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( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + } + + template + static HandleType GetHandle(const LocalRef& instance) { + return GetHandle(instance.Env(), instance.Get()); + } + + friend class NativeWeakPtrHolder; +}; + +} // 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 + * { + * // ... + * + * template + * class ProxyRunnable final : public Runnable + * { + * Functor mCall; + * public: + * ProxyRunnable(Functor&& call) : mCall(std::move(call)) {} + * virtual void run() override { mCall(); } + * }; + * + * public: + * template + * 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 +struct ProxyArg { + static_assert(std::is_trivial_v && std::is_standard_layout_v, + "T must be primitive type"); + + // Primitive types can be saved by value. + typedef T Type; + typedef typename TypeAdapter::JNIType JNIType; + + static void Clear(JNIEnv* env, Type&) {} + + static Type From(JNIEnv* env, JNIType val) { + return TypeAdapter::ToNative(env, val); + } +}; + +template +struct ProxyArg> { + // Ref types need to be saved by global ref. + typedef typename C::GlobalRef Type; + typedef typename TypeAdapter>::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 +struct ProxyArg : ProxyArg {}; +template <> +struct ProxyArg : ProxyArg {}; +template +struct ProxyArg> : ProxyArg {}; + +// ProxyNativeCall implements the functor object that is passed to OnNativeCall +template +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; + using ThisArgJNIType = std::conditional_t; + + // 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, + 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::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 + std::enable_if_t Call( + const Class::LocalRef& cls, std::index_sequence) const { + (*mNativeCall)(cls, std::get(mArgs)...); + } + + template + std::enable_if_t Call( + const Class::LocalRef& cls, std::index_sequence) const { + (*mNativeCall)(std::get(mArgs)...); + } + + template + std::enable_if_t Call( + const typename Owner::LocalRef& inst, + std::index_sequence) const { + auto impl = NativePtrTraits::Access(NativePtrTraits::Get(inst)); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(inst, std::get(mArgs)...); + } + + template + std::enable_if_t Call( + const typename Owner::LocalRef& inst, + std::index_sequence) const { + auto impl = NativePtrTraits::Access(NativePtrTraits::Get(inst)); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(std::get(mArgs)...); + } + + template + void Clear(JNIEnv* env, std::index_sequence) { + int dummy[] = { + (ProxyArg::Clear(env, std::get(mArgs)), 0)...}; + mozilla::Unused << dummy; + } + + static decltype(auto) GetNativeObject(Class::Param thisArg) { + return nullptr; + } + + static decltype(auto) GetNativeObject(typename Owner::Param thisArg) { + return NativePtrTraits::Access( + NativePtrTraits::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::JNIType... args) + : mNativeCall(nativeCall), + mThisArg(env, ThisArgClass::Ref::From(thisArg)), + mArgs(ProxyArg::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 + 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 + void SetTarget(T&&) const { + MOZ_CRASH(); + } + + void operator()() { + JNIEnv* const env = GetEnvForThread(); + typename ThisArgClass::LocalRef thisArg(env, mThisArg); + Call(thisArg, std::index_sequence_for{}); + + // 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{}); + mThisArg.Clear(env); + } +}; + +template +struct Dispatcher { + template + static std::enable_if_t + Run(ProxyArgs&&... args) { + Impl::OnNativeCall( + ProxyNativeCall(std::forward(args)...)); + } + + template + 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((HasThisArg || !IsStatic) ? thisArg : nullptr, + std::forward(args)...); + DispatchToGeckoPriorityQueue( + NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy))); + } + + template + static std::enable_if_t + 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((HasThisArg || !IsStatic) ? thisArg : nullptr, + std::forward(args)...); + NS_DispatchToMainThread( + NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy))); + } + + template + static std::enable_if_t + 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 NativeStub; + +template +class NativeStub> { + using Owner = typename Traits::Owner; + using ReturnType = typename Traits::ReturnType; + + static constexpr bool isStatic = Traits::isStatic; + static constexpr bool isVoid = std::is_void_v; + + struct VoidType { + using JNIType = void; + }; + using ReturnJNIType = + typename std::conditional_t>::JNIType; + + using ReturnTypeForNonVoidInstance = + std::conditional_t; + using ReturnTypeForVoidInstance = + std::conditional_t; + using ReturnTypeForNonVoidStatic = + std::conditional_t; + using ReturnTypeForVoidStatic = + std::conditional_t; + + static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid, + "Dispatched calls must have void return type"); + + public: + // Non-void instance method + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto impl = NativePtrTraits::Access( + NativePtrTraits::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return ReturnJNIType(); + } + return TypeAdapter::FromNative( + env, (impl->*Method)(TypeAdapter::ToNative(env, args)...)); + } + + // Non-void instance method with instance reference + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto impl = NativePtrTraits::Access( + NativePtrTraits::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::FromNative( + env, (impl->*Method)(self, TypeAdapter::ToNative(env, args)...)); + self.Forget(); + return res; + } + + // Void instance method + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + instance, Method, env, args...); + return; + } + + auto impl = NativePtrTraits::Access( + NativePtrTraits::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return; + } + (impl->*Method)(TypeAdapter::ToNative(env, args)...); + } + + // Void instance method with instance reference + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + instance, Method, env, args...); + return; + } + + auto impl = NativePtrTraits::Access( + NativePtrTraits::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::ToNative(env, args)...); + self.Forget(); + } + + // Overload for DisposeNative + template + 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::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 + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jclass, typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + return TypeAdapter::FromNative( + env, (*Method)(TypeAdapter::ToNative(env, args)...)); + } + + // Non-void static method with class reference + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jclass cls, typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto clazz = Class::LocalRef::Adopt(env, cls); + const auto res = TypeAdapter::FromNative( + env, (*Method)(clazz, TypeAdapter::ToNative(env, args)...)); + clazz.Forget(); + return res; + } + + // Void static method + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + cls, Method, env, args...); + return; + } + + (*Method)(TypeAdapter::ToNative(env, args)...); + } + + // Void static method with class reference + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + cls, Method, env, args...); + return; + } + + auto clazz = Class::LocalRef::Adopt(env, cls); + (*Method)(clazz, TypeAdapter::ToNative(env, args)...); + clazz.Forget(); + } +}; + +// Generate a JNINativeMethod from a native +// method's traits class and a wrapped stub. +template +constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*, + Args...)) { + return {Traits::name, Traits::signature, reinterpret_cast(stub)}; +} + +// Class inherited by implementing class. +template +class NativeImpl { + typedef typename Cls::template Natives 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::value == NativePtrType::WEAK_INTRUSIVE, + "Use another AttachNative for non-WeakPtr usage"); + return NativePtrTraits::Set(instance, static_cast(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, + UniquePtr&& ptr) { + static_assert(NativePtrPicker::value == NativePtrType::OWNING, + "Use another AttachNative for WeakPtr or RefPtr usage"); + return NativePtrTraits::Set(instance, std::move(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) { + static_assert(NativePtrPicker::value == NativePtrType::REFPTR, + "Use another AttachNative for non-RefPtr usage"); + return NativePtrTraits::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::Get(instance); + } + + static void DisposeNative(const typename Cls::LocalRef& instance) { + NativePtrTraits::Clear(instance); + } + + NativeImpl() { + // Initialize on creation if not already initialized. + Init(); + } +}; + +// Define static member. +template +bool NativeImpl::sInited; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Natives_h__ -- cgit v1.2.3