native/jni_scoped_helpers.cpp (369 lines of code) (raw):

// 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. #include <algorithm> #include <mutex> #include "jni_scoped_helpers.h" #include "client_handler.h" #include "jni_util.h" namespace { // Retrieves the JNIEnv for the current thread. Attaches the VM to the current // thread if necessary. Sets |mustDetach| to true if DetachJNIEnv must be // called. jint GetJNIEnv(JNIEnv** env, bool* mustDetach) { *env = nullptr; *mustDetach = false; JavaVM* jvm = GetJVM(); if (!jvm) return JNI_ERR; jint result = jvm->GetEnv((void**)env, JNI_VERSION_1_6); if (result == JNI_EDETACHED) { result = jvm->AttachCurrentThreadAsDaemon((void**)env, nullptr); if (result == JNI_OK) { *mustDetach = true; } } return result; } // Detaches the current thread from the VM. Should only be called if // |mustDetach| was set to true by GetJNIEnv. void DetachJNIEnv() { JavaVM* jvm = GetJVM(); if (jvm) { jvm->DetachCurrentThread(); } } // Using a simple cache to store global refs to loaded classes, since we // need to load the same classes over and over, which should neither change // on the JVM side nor be GCed... std::map<std::string, jobject> classCache_; std::mutex classCacheMutex_; // ...except if there's a change in the classloader to use by JCEF, in which // case the cache can be invalidated jobject classCacheClassLoader_; // Returns a class with the given fully qualified |class_name| (with '/' as // separator). jclass FindClass(JNIEnv* env, const char* class_name) { std::string classNameSeparatedByDots(class_name); std::replace(classNameSeparatedByDots.begin(), classNameSeparatedByDots.end(), '/', '.'); jobject classLoader = GetJavaClassLoader(); ASSERT(classLoader); // std::map is not thread-safe with regard to concurrent reading and writing std::lock_guard<std::mutex> guard(classCacheMutex_); if (classLoader != classCacheClassLoader_) { for (std::pair<const std::string, jobject>& entry : classCache_) { env->DeleteGlobalRef(entry.second); } classCache_.clear(); classCacheClassLoader_ = classLoader; } std::map<std::string, jobject>::iterator it = classCache_.find(classNameSeparatedByDots); if (it != classCache_.end()) { return static_cast<jclass>(env->NewLocalRef(it->second)); } ScopedJNIString classNameJString(env, classNameSeparatedByDots); jobject result = nullptr; JNI_CALL_METHOD(env, classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", Object, result, classNameJString.get()); // Make a global reference out of the local reference to allow for caching. // This produces a non-garbage-collectable class, since this global ref is // never released! However, for the classes that are requested by JCEF via // this mechanism, that should be acceptable, because they aren't candidates // to be GCed anyway. classCache_[classNameSeparatedByDots] = env->NewGlobalRef(result); return static_cast<jclass>(result); } jobject NewJNIBoolRef(JNIEnv* env, bool initValue) { ScopedJNIObjectLocal jboolRef(env, "org/cef/misc/BoolRef"); if (!jboolRef) return nullptr; SetJNIBoolRef(env, jboolRef, initValue); return jboolRef.Release(); } jobject NewJNIIntRef(JNIEnv* env, int initValue) { ScopedJNIObjectLocal jintRef(env, "org/cef/misc/IntRef"); if (!jintRef) return nullptr; SetJNIIntRef(env, jintRef, initValue); return jintRef.Release(); } jobject NewJNIStringRef(JNIEnv* env, const CefString& initValue) { ScopedJNIObjectLocal jstringRef(env, "org/cef/misc/StringRef"); if (!jstringRef) return nullptr; SetJNIStringRef(env, jstringRef, initValue); return jstringRef.Release(); } jobject NewJNIDate(JNIEnv* env, const CefBaseTime& time) { ScopedJNIObjectLocal jdate(env, "java/util/Date"); if (!jdate) return nullptr; CefTime cef_time; cef_time_from_basetime(time, &cef_time); double timestamp = cef_time.GetDoubleT() * 1000; JNI_CALL_VOID_METHOD(env, jdate, "setTime", "(J)V", (jlong)timestamp); return jdate.Release(); } jobject NewJNICookie(JNIEnv* env, const CefCookie& cookie) { ScopedJNIString name(env, CefString(&cookie.name)); ScopedJNIString value(env, CefString(&cookie.value)); ScopedJNIString domain(env, CefString(&cookie.domain)); ScopedJNIString path(env, CefString(&cookie.path)); const bool hasExpires = (cookie.has_expires != 0); ScopedJNIObjectLocal expires( env, hasExpires ? NewJNIDate(env, cookie.expires) : nullptr); ScopedJNIDate creation(env, cookie.creation); ScopedJNIDate last_access(env, cookie.last_access); return NewJNIObject(env, "org/cef/network/CefCookie", "(Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;Ljava/lang/String;" "ZZLjava/util/Date;Ljava/util/Date;" "ZLjava/util/Date;)V", name.get(), value.get(), domain.get(), path.get(), (cookie.secure != 0 ? JNI_TRUE : JNI_FALSE), (cookie.httponly != 0 ? JNI_TRUE : JNI_FALSE), creation.get(), last_access.get(), (hasExpires ? JNI_TRUE : JNI_FALSE), expires.get()); } jobject NewJNITransitionType(JNIEnv* env, CefRequest::TransitionType transitionType) { ScopedJNIObjectResult result(env); switch (transitionType & TT_SOURCE_MASK) { default: JNI_CASE(env, "org/cef/network/CefRequest$TransitionType", TT_LINK, result); JNI_CASE(env, "org/cef/network/CefRequest$TransitionType", TT_EXPLICIT, result); JNI_CASE(env, "org/cef/network/CefRequest$TransitionType", TT_AUTO_SUBFRAME, result); JNI_CASE(env, "org/cef/network/CefRequest$TransitionType", TT_MANUAL_SUBFRAME, result); JNI_CASE(env, "org/cef/network/CefRequest$TransitionType", TT_FORM_SUBMIT, result); JNI_CASE(env, "org/cef/network/CefRequest$TransitionType", TT_RELOAD, result); } if (result) { const int qualifiers = (transitionType & TT_QUALIFIER_MASK); JNI_CALL_VOID_METHOD(env, result, "addQualifiers", "(I)V", qualifiers); } return result.Release(); } jobject NewJNIURLRequestStatus( JNIEnv* env, CefResourceRequestHandler::URLRequestStatus status) { ScopedJNIObjectResult result(env); switch (status) { default: JNI_CASE(env, "org/cef/network/CefURLRequest$Status", UR_UNKNOWN, result); JNI_CASE(env, "org/cef/network/CefURLRequest$Status", UR_SUCCESS, result); JNI_CASE(env, "org/cef/network/CefURLRequest$Status", UR_IO_PENDING, result); JNI_CASE(env, "org/cef/network/CefURLRequest$Status", UR_CANCELED, result); JNI_CASE(env, "org/cef/network/CefURLRequest$Status", UR_FAILED, result); } return result.Release(); } jobject GetJNIBrowser(JNIEnv* env, CefRefPtr<CefBrowser> browser) { if (!browser) return nullptr; CefRefPtr<ClientHandler> client = (ClientHandler*)browser->GetHost()->GetClient().get(); return client->getBrowser(env, browser); } } // namespace // static const int ScopedJNIEnv::kDefaultLocalCapacity = 1024; ScopedJNIEnv::ScopedJNIEnv(jint local_capacity) : ScopedJNIEnv(nullptr, local_capacity) {} ScopedJNIEnv::ScopedJNIEnv(JNIEnv* env, jint local_capacity) : jenv_(env), local_capacity_(local_capacity) { if (!jenv_) { if (GetJNIEnv(&jenv_, &should_detach_) != JNI_OK || !jenv_) { NOTREACHED() << "Failed to retrieve JNIEnv"; return; } } if (local_capacity_ > 0) { if (jenv_->EnsureLocalCapacity(local_capacity_) != JNI_OK || jenv_->PushLocalFrame(local_capacity_) != JNI_OK) { LOG(WARNING) << "Failed to create local frame with capacity " << local_capacity_; local_capacity_ = 0; } } } ScopedJNIEnv::~ScopedJNIEnv() { if (!jenv_) return; if (local_capacity_ > 0) { if (export_result_) { *export_result_ = jenv_->PopLocalFrame(*export_result_); } else { jenv_->PopLocalFrame(nullptr); } } if (should_detach_) { DetachJNIEnv(); } } ScopedJNIObjectGlobal::ScopedJNIObjectGlobal(JNIEnv* env, jobject handle) : jhandle_(nullptr) { if (handle) { jhandle_ = env->NewGlobalRef(handle); DCHECK(jhandle_); } } ScopedJNIObjectGlobal::~ScopedJNIObjectGlobal() { if (jhandle_) { ScopedJNIEnv env; if (env) env->DeleteGlobalRef(jhandle_); } } jobject ScopedJNIObjectGlobal::get() const { return jhandle_; } ScopedJNIObjectGlobal::operator jobject() const { return jhandle_; } ScopedJNIObjectLocal::ScopedJNIObjectLocal(JNIEnv* env, jobject handle) : ScopedJNIBase<jobject>(env) { jhandle_ = handle; } ScopedJNIObjectLocal::ScopedJNIObjectLocal(JNIEnv* env, const char* class_name) : ScopedJNIObjectLocal(env, NewJNIObject(env, class_name)) {} ScopedJNIObjectResult::ScopedJNIObjectResult(JNIEnv* env) : ScopedJNIBase<jobject>(env) {} ScopedJNIClass::ScopedJNIClass(JNIEnv* env, const char* class_name) : ScopedJNIClass(env, FindClass(env, class_name)) {} ScopedJNIClass::ScopedJNIClass(JNIEnv* env, const jclass& cls) : ScopedJNIBase<jclass>(env) { jhandle_ = cls; } ScopedJNIString::ScopedJNIString(JNIEnv* env, const CefString& str) : ScopedJNIBase<jstring>(env) { jhandle_ = NewJNIString(env, str); DCHECK(jhandle_); } ScopedJNIDate::ScopedJNIDate(JNIEnv* env, const CefBaseTime& time) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNIDate(env, time); DCHECK(jhandle_); } ScopedJNICookie::ScopedJNICookie(JNIEnv* env, const CefCookie& cookie) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNICookie(env, cookie); DCHECK(jhandle_); } ScopedJNITransitionType::ScopedJNITransitionType( JNIEnv* env, CefRequest::TransitionType transitionType) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNITransitionType(env, transitionType); DCHECK(jhandle_); } ScopedJNIURLRequestStatus::ScopedJNIURLRequestStatus( JNIEnv* env, CefResourceRequestHandler::URLRequestStatus status) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNIURLRequestStatus(env, status); DCHECK(jhandle_); } ScopedJNIStringResult::ScopedJNIStringResult(JNIEnv* env) : ScopedJNIBase<jstring>(env) {} ScopedJNIStringResult::ScopedJNIStringResult(JNIEnv* env, const jstring& str) : ScopedJNIStringResult(env) { jhandle_ = str; } CefString ScopedJNIStringResult::GetCefString() const { if (!jhandle_) return CefString(); return GetJNIString(env_, jhandle_); } ScopedJNIBrowser::ScopedJNIBrowser(JNIEnv* env, CefRefPtr<CefBrowser> obj) : ScopedJNIBase<jobject>(env) { if (obj) { // Will return nullptr for browsers that represent native popup windows. jhandle_ = GetJNIBrowser(env_, obj); } else { jhandle_ = nullptr; } } void ScopedJNIBrowser::SetHandle(jobject handle, bool should_delete) { DCHECK(!jhandle_); DCHECK(handle); jhandle_ = handle; delete_ref_ = should_delete; } CefRefPtr<CefBrowser> ScopedJNIBrowser::GetCefObject() const { if (!jhandle_) return nullptr; return GetJNIBrowser(env_, jhandle_); } ScopedJNIAuthCallback::ScopedJNIAuthCallback(JNIEnv* env, CefRefPtr<CefAuthCallback> obj) : ScopedJNIObject<CefAuthCallback>(env, obj, "org/cef/callback/CefAuthCallback_N", "CefAuthCallback") {} ScopedJNIDragData::ScopedJNIDragData(JNIEnv* env, CefRefPtr<CefDragData> obj) : ScopedJNIObject<CefDragData>(env, obj, "org/cef/callback/CefDragData_N", "CefDragData") {} ScopedJNIFrame::ScopedJNIFrame(JNIEnv* env, CefRefPtr<CefFrame> obj) : ScopedJNIObject<CefFrame>(env, obj, "org/cef/browser/CefFrame_N", "CefFrame") {} ScopedJNIMenuModel::ScopedJNIMenuModel(JNIEnv* env, CefRefPtr<CefMenuModel> obj) : ScopedJNIObject<CefMenuModel>(env, obj, "org/cef/callback/CefMenuModel_N", "CefMenuModel") {} ScopedJNIMessageRouter::ScopedJNIMessageRouter(JNIEnv* env, CefRefPtr<CefMessageRouter> obj) : ScopedJNIObject<CefMessageRouter>(env, obj, "org/cef/browser/CefMessageRouter_N", "CefMessageRouter") {} ScopedJNIPostData::ScopedJNIPostData(JNIEnv* env, CefRefPtr<CefPostData> obj) : ScopedJNIObject<CefPostData>(env, obj, "org/cef/network/CefPostData_N", "CefPostData") {} ScopedJNIPostDataElement::ScopedJNIPostDataElement( JNIEnv* env, CefRefPtr<CefPostDataElement> obj) : ScopedJNIObject<CefPostDataElement>( env, obj, "org/cef/network/CefPostDataElement_N", "CefPostDataElement") {} ScopedJNIPrintSettings::ScopedJNIPrintSettings(JNIEnv* env, CefRefPtr<CefPrintSettings> obj) : ScopedJNIObject<CefPrintSettings>(env, obj, "org/cef/misc/CefPrintSettings_N", "CefPrintSettings") {} ScopedJNIRequest::ScopedJNIRequest(JNIEnv* env, CefRefPtr<CefRequest> obj) : ScopedJNIObject<CefRequest>(env, obj, "org/cef/network/CefRequest_N", "CefRequest") {} ScopedJNIResponse::ScopedJNIResponse(JNIEnv* env, CefRefPtr<CefResponse> obj) : ScopedJNIObject<CefResponse>(env, obj, "org/cef/network/CefResponse_N", "CefResponse") {} ScopedJNICallback::ScopedJNICallback(JNIEnv* env, CefRefPtr<CefCallback> obj) : ScopedJNIObject<CefCallback>(env, obj, "org/cef/callback/CefCallback_N", "CefCallback") {} ScopedJNIBoolRef::ScopedJNIBoolRef(JNIEnv* env, bool value) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNIBoolRef(env, value); DCHECK(jhandle_); } ScopedJNIBoolRef::operator bool() const { return GetJNIBoolRef(env_, jhandle_); } ScopedJNIIntRef::ScopedJNIIntRef(JNIEnv* env, int value) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNIIntRef(env, value); DCHECK(jhandle_); } ScopedJNIIntRef::operator int() const { return GetJNIIntRef(env_, jhandle_); } ScopedJNIStringRef::ScopedJNIStringRef(JNIEnv* env, const CefString& value) : ScopedJNIBase<jobject>(env) { jhandle_ = NewJNIStringRef(env, value); DCHECK(jhandle_); } ScopedJNIStringRef::operator CefString() const { return GetJNIStringRef(env_, jhandle_); }