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_);
}