// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. #ifndef JCEF_NATIVE_JNI_SCOPED_HELPERS_H_ #define JCEF_NATIVE_JNI_SCOPED_HELPERS_H_ #include #include #include "include/cef_auth_callback.h" #include "include/cef_browser.h" #include "include/cef_drag_data.h" #include "include/cef_frame.h" #include "include/cef_menu_model.h" #include "include/cef_print_settings.h" #include "include/cef_request.h" #include "include/cef_resource_request_handler.h" #include "include/cef_response.h" #include "include/wrapper/cef_message_router.h" // // -------- // OVERVIEW // -------- // // JCEF uses JNI bindings to support two-way communication between Java objects // and native C++ objects. The life span of the binding may differ depending // on the intended usage, and the Java and/or C++ objects may still exist after // the binding has been terminated. Consequently, care must be taken to follow // the intended usage for each object type. // // Some common usage patterns are: // // - Objects created from C++ code with life span terminated after execution // of a single Java method callback (see `EXAMPLE 1` below). // - Objects created from Java code with life span terminated via execution of // the Java dispose() or finalize() method (see `EXAMPLE 2` below). // - C++ objects that wrap Java objects for the purpose of executing Java // method callbacks with life span terminated on deletion of the C++ object // (see `EXAMPLE 3` below). // // The Java object representing a binding is often associated with the C++ // object via the CefNative Java interface. When a C++ object is created the // native code increments the C++ reference count and calls the setNativeRef // method on the JNI handle. The C++ pointer address is passed to JNI using the // `jlong` type and stored in the Java object using a `long N_CefHandle` member. // This `long` value can be passed into future JNI function calls from Java code // or retrieved from C++ code by calling the getNativeRef method on the JNI // handle. When the binding is terminated (via a call to dispose()/finalize() or // from C++ code) the setNativeRef method is called with a value of 0 and the // C++ reference count is decremented, possibly triggering deletion of the C++ // object. // // A C++ object that executes callbacks on a Java object will hold a global // reference to that Java object's JNI handle. This will keep the Java object // alive until after all of the associated C++ objects have been deleted. In // most cases these Java objects will not extend CefNative and a new C++ object // will be created on each callback. // // CefClient is a special case where the C++ object wraps a JNI handle and the // Java object extends CefNative to avoid creation of a new C++ object on // each callback. Care is taken with CefClient to explicitly release the C++ // references on shutdown (via remove*Handler methods) and thereby avoid a // reference loop between the C++ object and JNI handle. // // Below are examples of the common usage patterns for JCEF bindings. // // // --------- // EXAMPLE 1 // --------- // // An object implemented in native code that is only ever created from native // code and then passed to a Java callback. // // In this example CefSomeObject is implemented in native code and exposed to // Java via an equivalent interface. The native object is associated with the // Java object via CefNative, with life span controlled by the native caller. // Because the Java object is scoped to the native method there's no need to // support destruction via dispose() or finalize() from the Java side. // // For a complete example see CefContextMenuHandler.onBeforeContextMenu and // CefMenuModel. // // Java client usage: // // // Extending a CefSomeHandler interface that has an onCallback method. // class MyHandler extends CefSomeHandler { // // Client callback that will executed from native code. // @Override // public void onCallback(CefSomeObject someObject) { // // |someObject| is backed by a native CEF object and only valid for the // // scope of this method. // int something = someObject.getSomething(); // } // } // // Java implementation of org.cef.handler.CefSomeObject: // // // Only instantiated via CefSomeObject_N. // public interface CefSomeObject { // // Public interface methods... // public abstract int getSomething(); // } // // Java implementation of org.cef.handler.CefSomeObject_N: // // // Created from SomeHandler::OnCallback in native code. // class CefSomeObject_N extends CefNativeAdapter implements CefSomeObject { // CefSomeObject_N() {} // // // CefSomeObject implementation: // @Override // public int getSomething() { // try { // // Pass the native handle as a parameter for faster lookup. // return N_GetSomething(getNativeRef(null)); // } catch (UnsatisfiedLinkError ule) { // ule.printStackTrace(); // return -1; // } // } // // // This becomes a native JNI binding. // private final native int N_GetSomething(long self); // } // // C++ implementation of CefSomeObject_N.cpp: // // CefRefPtr GetSelf(jlong self) { // return reinterpret_cast(self); // } // // // Native implementation of N_GetSomething(). // JNIEXPORT jint JNICALL // Java_org_cef_handler_CefSomeObject_1N_N_1GetSomething(JNIEnv* env, // jobject obj, // jlong self) { // CefRefPtr object = GetSelf(self); // if (!object) // return -1; // return object->GetSomething(); // } // // C++ implementation of the SomeHandler::OnCallback method: // // // Helper for creating a CefSomeObject JNI handle. // class ScopedJNISomeObject : public ScopedJNIObject { // public: // ScopedJNISomeObject(JNIEnv* env, CefRefPtr obj) // : ScopedJNIObject(env, // obj, // "org/cef/handler/CefSomeObject_N", // "CefSomeObject") {} // }; // // void SomeHandler::OnCallback() { // // Create the JNI instance of CefSomeObject. // ScopedJNISomeObject jsomeObject(env, model); // // Disassociate from the Java object when |jsomeObject| is destroyed. // jsomeObject.SetTemporary(); // // // Pass the CefSomeObject instance to CefSomeHandler.onCallback. // JNI_CALL_VOID_METHOD(env, handle_, "onCallback", // "(Lorg/cef/handler/CefSomeObject;)V", // jsomeObject.get()); // } // // // --------- // EXAMPLE 2 // --------- // // An object implemented in native code that can be created and accessed from // Java code. // // In this example CefSomeObject is implemented in native code and exposed to // Java via an equivalent interface. The native object is associated with the // Java object via CefNative, with life span controlled by the Java object. // // For a complete example see CefDragData. // // Java client usage: // // // Create and use a CefSomeObject which is backed by a native CEF object. // CefSomeObject someObject = CefSomeObject.create(); // int something = someObject.getSomething(); // // // Explicitly release the native CEF object when done. If we don't do this // // the object may eventually be released via finalize() if/when garbage // // collection is triggered. // someObject.dispose(); // // Java implementation of org.cef.handler.CefSomeObject: // // public abstract class CefSomeObject { // // Public interface methods... // public abstract int getSomething(); // // // This constructor can't be called directly. Use create() instead. // CefSomeObject() {} // // // Cleanup in case the user forgets to call dispose(). // @Override // protected void finalize() throws Throwable { // dispose(); // super.finalize(); // } // // // Implemented in CefSomeObject_N to terminate the native binding. // public abstract void dispose(); // // public static final CefSomeObject create() { // return CefSomeObject_N.createNative(); // } // } // // Java implementation of org.cef.handler.CefSomeObject_N: // // class CefSomeObject_N extends CefSomeObject implements CefNative { // // CefNative implementation: // // Used internally to store a pointer to the CEF object. // private long N_CefHandle = 0; // // @Override // public void setNativeRef(String identifer, long nativeRef) { // N_CefHandle = nativeRef; // } // // @Override // public long getNativeRef(String identifer) { // return N_CefHandle; // } // // CefSomeObject_N() { // super(); // } // // // CefSomeObject implementation: // @Override // public void dispose() { // try { // N_Dispose(); // } catch (UnsatisfiedLinkError ule) { // ule.printStackTrace(); // } // } // // public static CefDragData createNative() { // try { // return CefSomeObject_N.N_Create(); // } catch (UnsatisfiedLinkError ule) { // ule.printStackTrace(); // return null; // } // } // // @Override // public int getSomething() { // try { // // Pass the native handle as a parameter for faster lookup. // return N_GetSomething(N_CefHandle); // } catch (UnsatisfiedLinkError ule) { // ule.printStackTrace(); // return -1; // } // } // // // These become native JNI bindings. // private final native static CefSomeObject_N N_Create(); // private final native void N_Dispose(); // private final native int N_GetSomething(long self); // } // // C++ implementation of CefSomeObject_N.cpp: // // const char kCefClassName[] = "CefSomeObject"; // // CefRefPtr GetSelf(JNIEnv* env, jlong self) { // return reinterpret_cast(self); // } // // // Helper for creating a CefSomeObject JNI handle. // class ScopedJNISomeObject : public ScopedJNIObject { // public: // ScopedJNISomeObject(JNIEnv* env, CefRefPtr obj) // : ScopedJNIObject(env, // obj, // "org/cef/handler/CefSomeObject_N", // kCefClassName) {} // }; // // JNIEXPORT void JNICALL // Java_org_cef_handler_CefSomeObject_1N_N_1Dispose(JNIEnv* env, // jobject obj) { // // Terminate the binding and release the native object reference that // // was taken by ScopedJNISomeObject. The native object may be destroyed. // SetCefForJNIObject(env, obj, nullptr, kCefClassName); // } // // JNIEXPORT jobject JNICALL // Java_org_cef_handler_CefSomeObject_1N_N_1Create(JNIEnv* env, jclass cls) { // CefRefPtr object = CefSomeObject::Create(); // // // Create and return a new JNI handle that holds a reference to |object|. // ScopedJNISomeObject jobject(env, object); // return jobject.Release(); // } // // JNIEXPORT jint JNICALL // Java_org_cef_handler_CefSomeObject_1N_N_1GetSomething(JNIEnv* env, // jobject obj, // jlong self) { // CefRefPtr object = GetSelf(env, self); // if (!object) // return -1; // return object->GetSomething(); // } // // // --------- // EXAMPLE 3 // --------- // // A native interface that delegates callbacks to a Java interface. The native // object is not associated with the Java object so multiple calls to // getSomeHandler will create a new native object. // // In this example the CefSomeHandler interface exists in both native code and // Java code. The client returns an instance of CefSomeHandler from the // CefSourceHandler.getSomeHandler Java method. Native code then calls the // CefSomeHandler.doSomething method at a later time. // // For a complete example see CefDisplayHandler. // // Java client usage: // // // Client implementation of CefSomeHandler. // class MySomeHandler extends CefSomeHandlerAdapter { // MySomeHandler() {} // // @Override // public void doSomething() { /* Do something here... */ } // } // // // Client implementation of an interface that returns a CefSomeHandler. // class MySourceHandler extends CefSourceHandlerAdapter { // @Override // public CefSomeHandler getSomeHandler() { return new MySomeHandler(); } // } // // // Somehow install the MySourceHandler and trigger a call to // // getSomeHandler() from native code. // CefContext.somehowInstallAndExecuteSourceHandler(new MySourceHandler()); // // Java implementation of org.cef.handler.CefSomeHandler: // // public interface CefSomeHandler { // public void DoSomething(); // } // // Java implementation of org.cef.handler.CefSomeHandlerAdapter: // // public abstract class CefSomeHandlerAdapter implements CefSomeHandler { // @Override // public void doSomething() {} // } // // C++ implementation of some_handler.h/cpp: // // class SomeHandler : public CefSomeHandler { // public: // SomeHandler(JNIEnv* env, jobject handler); // // // CefSomeHandler methods: // void DoSomething() override; // // private: // // Manages a global JNI reference to the Java object. // ScopedJNIObjectGlobal handle_; // }; // // SomeHandler::SomeHandler(JNIEnv* env, jobject handler) // : handle_(env, handler) {} // // void SomeHandler::DoSomething() { // ScopedJNIEnv env; // if (!env) // return; // JNI_CALL_VOID_METHOD(env, handle_, "doSomething", "()V"); // } // // C++ implementation of the SourceHandler::GetSomeHandler method: // // CefRefPtr SourceHandler::GetSomeHandler() { // ScopedJNIEnv env; // if (!env) // return nullptr; // // ScopedJNIObjectResult jresult(env); // // // Call CefSourceHandler.getSomeHandler() in Java code to get a // // CefSomeHandler object. // JNI_CALL_METHOD(env, handle_, "getSomeHandler", // "()Lorg/cef/handler/CefSomeHandler;", Object, jresult); // // // Create and return a new SomeHandler C++ object that wraps the Java // // object. // return new SomeHandler(env, jresult); // } // // Type specialization helpers for SetCefForJNIObject. struct SetCefForJNIObjectHelper { static inline void AddRef(CefBaseScoped* obj) {} static inline void Release(CefBaseScoped* obj) {} template static inline T* Get(CefRawPtr obj) { return obj; } static inline void AddRef(CefBaseRefCounted* obj) { obj->AddRef(); } static inline void Release(CefBaseRefCounted* obj) { obj->Release(); } template static inline T* Get(CefRefPtr obj) { return obj.get(); } // For ref-counted implementations that don't derive from CefBaseRefCounted. template static inline void AddRef(base::RefCountedThreadSafe* obj) { obj->AddRef(); } template static inline void Release(base::RefCountedThreadSafe* obj) { obj->Release(); } }; // Forward declarations required by the below template types. jobject NewJNIObject(JNIEnv* env, const char* class_name); template bool SetCefForJNIObject_sync(JNIEnv* env, jobject obj, T* base, const char* varName); template bool SetCefForJNIObject(JNIEnv* env, jobject obj, T* base, const char* varName); template T* GetCefFromJNIObject(JNIEnv* env, jobject obj, const char* varName); template CefRefPtr GetCefFromJNIObject_sync(JNIEnv* env, jobject obj, const char* varName); class ScopedJNIEnv { public: static const int kDefaultLocalCapacity; // Retrieve the JNIEnv for the current thread or attach the VM to the current // thread if necessary. // If |local_capacity| > 0 a local frame will be created with the specified // maximum number of local references. Otherwise, no local frame will be // created. ScopedJNIEnv(jint local_capacity = kDefaultLocalCapacity); // Like above, but using an already known JNIEnv. ScopedJNIEnv(JNIEnv* env, jint local_capacity = kDefaultLocalCapacity); ~ScopedJNIEnv(); // If a local frame was created, export |result| to the previous local // reference frame on destruction. void set_export_result(jobject* result) { export_result_ = result; } // Explicit return. JNIEnv* get() const { return jenv_; } // Implicit cast works in most cases. operator JNIEnv*() const { return jenv_; } // Pointer override for forwarding calls to the JNIEnv. JNIEnv* operator->() const { DCHECK(jenv_); return jenv_; } private: JNIEnv* jenv_; jint local_capacity_; bool should_detach_ = false; jobject* export_result_ = nullptr; }; // Used by the native counterpart of JCEF objects to hold a global JNI handle. class ScopedJNIObjectGlobal { public: // Creates a global reference to |handle|. ScopedJNIObjectGlobal(JNIEnv* env, jobject handle); ~ScopedJNIObjectGlobal(); // Explicit return. jobject get() const; // Implicit cast works in most cases. operator jobject() const; protected: jobject jhandle_; }; // Base class for scoped JNI types. Most derived classes will populate // |jhandle_| in the constructor. template class ScopedJNIBase { public: ScopedJNIBase(JNIEnv* env, bool should_delete = true) : env_(env), jhandle_(nullptr), delete_ref_(should_delete) { DCHECK(env_); } virtual ~ScopedJNIBase() { if (jhandle_ && delete_ref_) { env_->DeleteLocalRef(jhandle_); } } // Explicit return for use with JNI_CALL_METHOD. T get() const { return jhandle_; } // Implicit cast works in most cases. operator T() const { return jhandle_; } // Returns and disassociates from the underlying handle. T Release() { T temp = jhandle_; jhandle_ = nullptr; return temp; } protected: JNIEnv* const env_; T jhandle_; bool delete_ref_; }; // Manages a local handle to a JNI object. Use ScopedJNIObject instead if // the Java class implements CefNative. class ScopedJNIObjectLocal : public ScopedJNIBase { public: // A local reference to |handle| should already exist. ScopedJNIObjectLocal(JNIEnv* env, jobject handle); // Create a new instance of the specified |class_name|. ScopedJNIObjectLocal(JNIEnv* env, const char* class_name); }; // Used for later assignment of a handle whose lifespan will then be managed. // This will usually be a JNI object result parameter from JNI_CALL_METHOD. class ScopedJNIObjectResult : public ScopedJNIBase { public: explicit ScopedJNIObjectResult(JNIEnv* env); jobject& operator=(const jobject& obj) { DCHECK(!jhandle_); jhandle_ = obj; return jhandle_; } }; // Used for JNI objects that implement CefNative. template > class ScopedJNIObject : public ScopedJNIBase { public: // Attach to an existing JNI object handle. |should_delete| should be false // if the handle is a parameter to or return value from a JNI function that // this scoped object resides within. ScopedJNIObject(JNIEnv* env, jobject handle, bool should_delete, const char* cef_class_name) : ScopedJNIBase(env, should_delete), cef_class_name_(cef_class_name), temporary_(false), created_handle_(false) { DCHECK(handle); jhandle_ = handle; } // Create a new JNI object handle that attaches to an existing CEF object. A // reference will be taken to |obj| and associated with the JNI handle via the // CefNative Java interface. The reference will later be invalidated using one // of the following approaches, depending on the object's intended usage: // 1. Via a JNI Dispose callback that is executed from the Java object's // dispose() method. This is the preferred approach for long-lived objects // as it gives the Java application explicit control over the CEF object's // life span. // 2. Via execution of a JNI callback that represents completion of a single // use Cef*Callback object. // 3. Via this object's destructor if SetTemporary() was called. This approach // can be used to create Java objects that are only valid for the duration // of a JNI method call. // 4. Via a JNI Dispose callback that is executed from the Java object's // finalize() method. This is the catch-all case if none of the above // conditions trigger and should be used with caution because the // finalize() method is not guaranteed to be called in a timely manner. ScopedJNIObject(JNIEnv* env, PtrT obj, const char* jni_class_name, const char* cef_class_name) : ScopedJNIBase(env), object_(obj), cef_class_name_(cef_class_name), temporary_(false), created_handle_(false) { if (obj) { jhandle_ = NewJNIObject(env_, jni_class_name); if (jhandle_) { created_handle_ = true; SetCefForJNIObject(env_, jhandle_, SetCefForJNIObjectHelper::Get(obj), cef_class_name); } } } virtual ~ScopedJNIObject() { if (temporary_ && created_handle_) { // Invalidate the Java object. SetCefForJNIObject(env_, jhandle_, nullptr, cef_class_name_); } } // Attach to the specified |handle|. |should_delete| should be false if the // handle is a parameter to or return value from a JNI callback function. void SetHandle(jobject handle, bool should_delete) { DCHECK(!jhandle_); DCHECK(handle); jhandle_ = handle; delete_ref_ = should_delete; } // Invalidate the Java object on destruction. void SetTemporary() { DCHECK(created_handle_); temporary_ = true; } // Get an existing native CEF object. PtrT GetCefObject() { if (object_) return object_; if (jhandle_) object_ = GetCefFromJNIObject(env_, jhandle_, cef_class_name_); return object_; } // Get an existing or create a new native CEF object. PtrT GetOrCreateCefObject() { PtrT object = GetCefObject(); if (!object && jhandle_) { object = new T(env_, jhandle_); SetCefForJNIObject(env_, jhandle_, SetCefForJNIObjectHelper::Get(object), cef_class_name_); object_ = object; } return object; } protected: PtrT object_; const char* const cef_class_name_; bool temporary_; bool created_handle_; }; // JNI class. Finding |class_name| may fail. class ScopedJNIClass : public ScopedJNIBase { public: ScopedJNIClass(JNIEnv* env, const char* class_name); ScopedJNIClass(JNIEnv* env, const jclass& cls); }; // JNI string. class ScopedJNIString : public ScopedJNIBase { public: ScopedJNIString(JNIEnv* env, const CefString& str); }; // JNI date. class ScopedJNIDate : public ScopedJNIBase { public: ScopedJNIDate(JNIEnv* env, const CefBaseTime& time); }; // JNI cookie. class ScopedJNICookie : public ScopedJNIBase { public: ScopedJNICookie(JNIEnv* env, const CefCookie& cookie); }; // JNI TransitionType. class ScopedJNITransitionType : public ScopedJNIBase { public: ScopedJNITransitionType(JNIEnv* env, CefRequest::TransitionType transitionType); }; // JNI URLRequestStatus. class ScopedJNIURLRequestStatus : public ScopedJNIBase { public: ScopedJNIURLRequestStatus(JNIEnv* env, CefResourceRequestHandler::URLRequestStatus status); }; // Used for assignment of a handle whose lifespan will then be managed. class ScopedJNIStringResult : public ScopedJNIBase { public: explicit ScopedJNIStringResult(JNIEnv* env); ScopedJNIStringResult(JNIEnv* env, const jstring& str); jstring& operator=(const jstring& str) { DCHECK(!jhandle_); jhandle_ = str; return jhandle_; } jobject& operator=(const jobject& obj) { return (jobject&)operator=((jstring)obj); } // Get the associated CEF string. CefString GetCefString() const; }; // JNI CefBrowser object. This is a special case where the CefBrowser object // must always already exist. class ScopedJNIBrowser : public ScopedJNIBase { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIBrowser(JNIEnv* env, CefRefPtr obj = nullptr); // Attach to the specified |handle|. void SetHandle(jobject handle, bool should_delete); // Get the associated CEF object. CefRefPtr GetCefObject() const; }; // JNI CefAuthCallback object. class ScopedJNIAuthCallback : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIAuthCallback(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefDragData object. class ScopedJNIDragData : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIDragData(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefFrame object. class ScopedJNIFrame : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIFrame(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefMenuModel object. class ScopedJNIMenuModel : public ScopedJNIObject { public: // |obj| must be non-nullptr. ScopedJNIMenuModel(JNIEnv* env, CefRefPtr obj); }; // JNI CefMessageRouter object. using CefMessageRouter = CefMessageRouterBrowserSide; class ScopedJNIMessageRouter : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIMessageRouter(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefPostData object. class ScopedJNIPostData : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIPostData(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefPostDataElement object. class ScopedJNIPostDataElement : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIPostDataElement(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefPrintSettings object. class ScopedJNIPrintSettings : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIPrintSettings(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefRequest object. class ScopedJNIRequest : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIRequest(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefResponse object. class ScopedJNIResponse : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNIResponse(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI CefCallback object. class ScopedJNICallback : public ScopedJNIObject { public: // If |obj| is nullptr the SetHandle method should be used. ScopedJNICallback(JNIEnv* env, CefRefPtr obj = nullptr); }; // JNI BoolRef object. class ScopedJNIBoolRef : public ScopedJNIBase { public: ScopedJNIBoolRef(JNIEnv* env, bool value); // Implicit retrieval of the underlying value. operator bool() const; }; // JNI IntRef object. class ScopedJNIIntRef : public ScopedJNIBase { public: ScopedJNIIntRef(JNIEnv* env, int value); // Implicit retrieval of the underlying value. operator int() const; }; // JNI StringRef object. class ScopedJNIStringRef : public ScopedJNIBase { public: ScopedJNIStringRef(JNIEnv* env, const CefString& value); // Implicit retrieval of the underlying value. operator CefString() const; }; // Helper macros to call a method on the java side. #define JNI_CALL_METHOD(env, obj, method, sig, type, storeIn, ...) \ { \ if (env && obj) { \ ScopedJNIClass _cls(env, env->GetObjectClass(obj)); \ jmethodID _methodId = env->GetMethodID(_cls, method, sig); \ if (_methodId != nullptr) { \ storeIn = env->Call##type##Method(obj, _methodId, ##__VA_ARGS__); \ } \ if (env->ExceptionOccurred()) { \ env->ExceptionDescribe(); \ env->ExceptionClear(); \ } \ } \ } #define JNI_CALL_VOID_METHOD_EX(env, obj, method, sig, ...) \ { \ if (env && obj) { \ ScopedJNIClass _cls(env, env->GetObjectClass(obj)); \ jmethodID _methodId = env->GetMethodID(_cls, method, sig); \ if (_methodId != nullptr) { \ env->CallVoidMethod(obj, _methodId, ##__VA_ARGS__); \ } \ } \ } #define JNI_CALL_VOID_METHOD(env, obj, method, sig, ...) \ { \ if (env && obj) { \ ScopedJNIClass _cls(env, env->GetObjectClass(obj)); \ jmethodID _methodId = env->GetMethodID(_cls, method, sig); \ if (_methodId != nullptr) { \ env->CallVoidMethod(obj, _methodId, ##__VA_ARGS__); \ } \ if (env->ExceptionOccurred()) { \ env->ExceptionDescribe(); \ env->ExceptionClear(); \ } \ } \ } #define JNI_CALL_BOOLEAN_METHOD(out, env, obj, method, sig, ...) \ { \ if (env && obj) { \ ScopedJNIClass _cls(env, env->GetObjectClass(obj)); \ jmethodID _methodId = env->GetMethodID(_cls, method, sig); \ if (_methodId != nullptr) { \ out = env->CallBooleanMethod(obj, _methodId, ##__VA_ARGS__); \ } \ if (env->ExceptionOccurred()) { \ env->ExceptionDescribe(); \ env->ExceptionClear(); \ } \ } \ } // Set the CEF base object for an existing JNI object. A reference will be // added to the base object. If a previous base object existed a reference // will be removed from that object. template bool SetCefForJNIObject_sync(JNIEnv* env, jobject obj, T* base, const char* varName) { if (!obj) return false; ScopedJNIString identifer(env, varName); jlong previousValue = 0; JNI_CALL_METHOD(env, obj, "lockAndGetNativeRef", "(Ljava/lang/String;)J", Long, previousValue, identifer.get()); JNI_CALL_VOID_METHOD(env, obj, "setNativeRef", "(Ljava/lang/String;J)V", identifer.get(), (jlong)base); if (base) { // Add a reference to the new base object. SetCefForJNIObjectHelper::AddRef(base); } if (previousValue != 0) { // Remove a reference from the previous base object. // NOTE: must do it after setNativeRef_safe (otherwise we can create CefRefPtr with killed ptr) SetCefForJNIObjectHelper::Release(reinterpret_cast(previousValue)); } JNI_CALL_VOID_METHOD(env, obj, "unlock", "(Ljava/lang/String;)V", identifer.get()); return true; } // Set the CEF base object for an existing JNI object. A reference will be // added to the base object. If a previous base object existed a reference // will be removed from that object. template bool SetCefForJNIObject(JNIEnv* env, jobject obj, T* base, const char* varName) { if (!obj) return false; ScopedJNIString identifer(env, varName); jlong previousValue = 0; JNI_CALL_METHOD(env, obj, "getNativeRef", "(Ljava/lang/String;)J", Long, previousValue, identifer.get()); JNI_CALL_VOID_METHOD(env, obj, "setNativeRef", "(Ljava/lang/String;J)V", identifer.get(), (jlong)base); if (base) { // Add a reference to the new base object. SetCefForJNIObjectHelper::AddRef(base); } if (previousValue != 0) { // Remove a reference from the previous base object. // NOTE: must do it after setNativeRef_safe (otherwise we can create CefRefPtr with killed ptr) SetCefForJNIObjectHelper::Release(reinterpret_cast(previousValue)); } return true; } // Retrieve the CEF base object from an existing JNI object. template T* GetCefFromJNIObject(JNIEnv* env, jobject obj, const char* varName) { if (!obj) return nullptr; ScopedJNIString identifer(env, varName); jlong previousValue = 0; JNI_CALL_METHOD(env, obj, "getNativeRef", "(Ljava/lang/String;)J", Long, previousValue, identifer.get()); if (previousValue != 0) return reinterpret_cast(previousValue); return nullptr; } template CefRefPtr GetCefFromJNIObject_sync(JNIEnv* env, jobject obj, const char* varName) { if (!obj) return CefRefPtr(); ScopedJNIString identifer(env, varName); jlong value = 0; JNI_CALL_METHOD(env, obj, "lockAndGetNativeRef", "(Ljava/lang/String;)J", Long, value, identifer.get()); CefRefPtr result(reinterpret_cast(value)); // CefRefPtr ctor internally invokes value->AddRef // after that we can safely release lock JNI_CALL_VOID_METHOD(env, obj, "unlock", "(Ljava/lang/String;)V", identifer.get()); return result; } #endif // JCEF_NATIVE_JNI_SCOPED_HELPERS_H_